| // 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; |
| |
| static struct tracecmd_event_list all_event_list = { |
| .next = NULL, |
| .glob = "all" |
| }; |
| |
| struct tracecmd_option { |
| unsigned short id; |
| int size; |
| void *data; |
| tsize_t offset; |
| 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_t options_start; |
| bool big_endian; |
| |
| struct list_head options; |
| struct tracecmd_msg_handle *msg_handle; |
| char *trace_clock; |
| }; |
| |
| 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; |
| }; |
| |
| static stsize_t |
| do_write_check(struct tracecmd_output *handle, const void *data, tsize_t size) |
| { |
| if (handle->msg_handle) |
| return tracecmd_msg_data_send(handle->msg_handle, data, size); |
| |
| return __do_write_check(handle->fd, data, size); |
| } |
| |
| 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); |
| } |
| |
| /** |
| * 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) |
| 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; |
| |
| if (!handle) |
| return; |
| |
| if (handle->tracing_dir) |
| free(handle->tracing_dir); |
| |
| if (handle->pevent) |
| tep_unref(handle->pevent); |
| |
| 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->trace_clock); |
| free(handle); |
| } |
| |
| void tracecmd_output_close(struct tracecmd_output *handle) |
| { |
| if (!handle) |
| return; |
| |
| 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) |
| { |
| tsize_t size = 0; |
| char buf[BUFSIZ]; |
| stsize_t r; |
| |
| do { |
| r = read(fd, buf, BUFSIZ); |
| if (r > 0) { |
| size += r; |
| if (do_write_check(handle, buf, r)) |
| return 0; |
| } |
| } 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); |
| close(fd); |
| |
| return size; |
| } |
| |
| /* |
| * 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; |
| } |
| |
| static int read_header_files(struct tracecmd_output *handle) |
| { |
| tsize_t size, check_size, endian8; |
| struct stat st; |
| char *path; |
| int fd; |
| 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; |
| |
| 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)) |
| return -1; |
| size = 0; |
| if (do_write_check(handle, &size, 8)) |
| return -1; |
| if (do_write_check(handle, "header_event", 13)) |
| return -1; |
| if (do_write_check(handle, &size, 8)) |
| return -1; |
| return 0; |
| } |
| |
| fd = open(path, O_RDONLY); |
| if (fd < 0) { |
| tracecmd_warning("can't read '%s'", path); |
| return -1; |
| } |
| |
| /* 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); |
| close(fd); |
| if (size != check_size) { |
| tracecmd_warning("wrong size for '%s' size=%lld read=%lld", path, size, check_size); |
| errno = EINVAL; |
| return -1; |
| } |
| put_tracing_file(path); |
| |
| path = get_tracing_file(handle, "events/header_event"); |
| if (!path) |
| return -1; |
| |
| fd = open(path, O_RDONLY); |
| if (fd < 0) { |
| tracecmd_warning("can't read '%s'", path); |
| return -1; |
| } |
| |
| 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); |
| close(fd); |
| if (size != check_size) { |
| tracecmd_warning("wrong size for '%s'", path); |
| return -1; |
| } |
| put_tracing_file(path); |
| |
| handle->file_state = TRACECMD_FILE_HEADERS; |
| |
| return 0; |
| |
| out_close: |
| 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) |
| { |
| struct list_event_system *systems = NULL; |
| struct tracecmd_event_list list = { .glob = "ftrace/*" }; |
| 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; |
| } |
| |
| create_event_list_item(handle, &systems, &list); |
| |
| ret = copy_event_system(handle, systems); |
| |
| free_list_events(systems); |
| |
| 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) |
| { |
| 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; |
| 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 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 (list) |
| event_list = &all_events; |
| |
| systems = create_event_list(handle, event_list); |
| |
| for (slist = systems; slist; slist = slist->next) |
| count++; |
| |
| 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); |
| } |
| |
| handle->file_state = TRACECMD_FILE_ALL_EVENTS; |
| out_free: |
| 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, |
| const char *kallsyms) |
| { |
| unsigned int size, check_size, endian4; |
| const char *path = "/proc/kallsyms"; |
| 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; |
| |
| ret = stat(path, &st); |
| if (ret < 0) { |
| /* not found */ |
| size = 0; |
| endian4 = convert_endian_4(handle, size); |
| if (do_write_check(handle, &endian4, 4)) |
| return -1; |
| return 0; |
| } |
| size = get_size(path); |
| endian4 = convert_endian_4(handle, size); |
| if (do_write_check(handle, &endian4, 4)) |
| return -1; |
| |
| 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); |
| return -1; |
| } |
| set_proc_kptr_restrict(1); |
| |
| handle->file_state = TRACECMD_FILE_KALLSYMS; |
| |
| return 0; |
| } |
| |
| static int read_ftrace_printk(struct tracecmd_output *handle) |
| { |
| unsigned int size, check_size, endian4; |
| 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; |
| |
| 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: |
| handle->file_state = TRACECMD_FILE_PRINTK; |
| put_tracing_file(path); |
| return 0; |
| fail: |
| put_tracing_file(path); |
| 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; |
| } |
| |
| /** |
| * tracecmd_output_allocate - allocate new output handler to a trace file |
| * @handle: file descriptor to an empty file, it can be -1 if the handler |
| * will not write to a file |
| * |
| * This API only allocates a handler and performs minimal initialization. |
| * No data is written in the file. |
| * |
| * Returns pointer to a newly allocated file descriptor, that can be used |
| * with other library APIs. In case of an error, NULL is returned. The returned |
| * handler must be freed with tracecmd_output_close() or tracecmd_output_free() |
| */ |
| struct tracecmd_output *tracecmd_output_allocate(int fd) |
| { |
| struct tracecmd_output *handle; |
| |
| handle = calloc(1, sizeof(*handle)); |
| if (!handle) |
| return NULL; |
| |
| handle->fd = fd; |
| |
| handle->file_version = FILE_VERSION; |
| |
| handle->page_size = getpagesize(); |
| handle->big_endian = tracecmd_host_bigendian(); |
| |
| list_head_init(&handle->options); |
| |
| handle->file_state = TRACECMD_FILE_ALLOCATED; |
| |
| return handle; |
| } |
| |
| /** |
| * tracecmd_output_set_msg - associated an output file handler with network message handler |
| * @handle: output handler to a trace file. |
| * @msg_handle: network handler, allocated by tracecmd_msg_handle_alloc() |
| * |
| * This API associates an output file handler with a network stream. All subsequent API calls |
| * with this output file handler will send data over the network using the @msg_handle, instead |
| * of writing to a file. |
| * This API must be called after the handler file version is set and before |
| * tracecmd_output_write_init(). |
| * |
| * Returns 0 on success, or -1 if the output file handler is not allocated or not in expected state. |
| */ |
| int tracecmd_output_set_msg(struct tracecmd_output *handler, struct tracecmd_msg_handle *msg_handle) |
| { |
| if (!handler || handler->file_state != TRACECMD_FILE_ALLOCATED) |
| return -1; |
| |
| handler->msg_handle = msg_handle; |
| |
| return 0; |
| } |
| |
| /** |
| * tracecmd_output_set_trace_dir - Set a custom tracing dir, instead of system default |
| * @handle: output handler to a trace file. |
| * @tracing_dir: full path to a directory with tracing files |
| * |
| * This API associates an output file handler with a custom tracing directory, to be used when |
| * creating the trace file instead of the system default tracing directory. |
| * This API must be called before tracecmd_output_write_init(). |
| * |
| * Returns 0 on success, or -1 if the output file handler is not allocated or not in expected state. |
| */ |
| int tracecmd_output_set_trace_dir(struct tracecmd_output *handler, const char *tracing_dir) |
| { |
| if (!handler || handler->file_state != TRACECMD_FILE_ALLOCATED) |
| return -1; |
| |
| free(handler->tracing_dir); |
| if (tracing_dir) { |
| handler->tracing_dir = strdup(tracing_dir); |
| if (!handler->tracing_dir) |
| return -1; |
| } else |
| handler->tracing_dir = NULL; |
| |
| return 0; |
| } |
| |
| /** |
| * tracecmd_output_set_kallsyms - Set a custom kernel symbols file, instead of system default |
| * @handle: output handler to a trace file. |
| * @tracing_dir: full path to a file with kernel symbols |
| * |
| * This API associates an output file handler with a custom kernel symbols file, to be used when |
| * creating the trace file instead of the system default kernel symbols file. |
| * This API must be called before tracecmd_output_write_init(). |
| * |
| * Returns 0 on success, or -1 if the output file handler is not allocated or not in expected state. |
| */ |
| int tracecmd_output_set_kallsyms(struct tracecmd_output *handler, const char *kallsyms) |
| { |
| if (!handler || handler->file_state != TRACECMD_FILE_ALLOCATED) |
| return -1; |
| |
| free(handler->kallsyms); |
| if (kallsyms) { |
| handler->kallsyms = strdup(kallsyms); |
| if (!handler->kallsyms) |
| return -1; |
| } else |
| handler->kallsyms = NULL; |
| |
| return 0; |
| } |
| |
| /** |
| * tracecmd_output_set_from_input - Inherit parameters from an existing trace file |
| * @handle: output handler to a trace file. |
| * @ihandle: input handler to an existing trace file. |
| * |
| * This API copies parameters from input handler @ihandle, associated with an existing trace file, |
| * to the output handler @handle, associated with file that is going to be created. |
| * These parameters are copied: |
| * - tep handler |
| * - page size |
| * - file endian |
| * - file version |
| * - file compression protocol |
| * This API must be called before tracecmd_output_write_init(). |
| * |
| * Returns 0 on success, or -1 if the output file handler is not allocated or not in expected state. |
| */ |
| int tracecmd_output_set_from_input(struct tracecmd_output *handler, struct tracecmd_input *ihandle) |
| { |
| if (!handler || !ihandle || handler->file_state != TRACECMD_FILE_ALLOCATED) |
| return -1; |
| |
| /* get endian, page size, file version and compression */ |
| /* Use the pevent of the ihandle for later writes */ |
| handler->pevent = tracecmd_get_tep(ihandle); |
| tep_ref(handler->pevent); |
| handler->page_size = tracecmd_page_size(ihandle); |
| handler->file_version = tracecmd_get_in_file_version(ihandle); |
| handler->big_endian = tep_is_file_bigendian(handler->pevent); |
| |
| return 0; |
| } |
| |
| /** |
| * tracecmd_output_write_init - Write the initial magics in the trace file |
| * @handle: output handler to a trace file. |
| * |
| * This API must be called after all tracecmd_output_set_...() APIs and before writing anything |
| * to the trace file. This initial information is written in 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 handler is not allocated or not in expected state. |
| */ |
| int tracecmd_output_write_init(struct tracecmd_output *handler) |
| { |
| char buf[BUFSIZ]; |
| int endian4; |
| |
| if (!handler || handler->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(handler, buf, 10)) |
| return -1; |
| |
| sprintf(buf, "%lu", handler->file_version); |
| if (do_write_check(handler, buf, strlen(buf) + 1)) |
| return -1; |
| |
| if (handler->big_endian) |
| buf[0] = 1; |
| else |
| buf[0] = 0; |
| if (do_write_check(handler, buf, 1)) |
| return -1; |
| |
| /* save size of long (this may not be what the kernel is) */ |
| buf[0] = sizeof(long); |
| if (do_write_check(handler, buf, 1)) |
| return -1; |
| |
| endian4 = convert_endian_4(handler, handler->page_size); |
| if (do_write_check(handler, &endian4, 4)) |
| return -1; |
| handler->file_state = TRACECMD_FILE_INIT; |
| return 0; |
| } |
| |
| static int select_file_version(struct tracecmd_output *handle, |
| struct tracecmd_input *ihandle) |
| { |
| if (ihandle) |
| handle->file_version = tracecmd_get_in_file_version(ihandle); |
| else |
| handle->file_version = FILE_VERSION; |
| |
| return 0; |
| } |
| |
| static struct tracecmd_output * |
| create_file_fd(int fd, struct tracecmd_input *ihandle, |
| const char *tracing_dir, |
| const char *kallsyms, |
| struct tracecmd_event_list *list, |
| struct tracecmd_msg_handle *msg_handle) |
| { |
| struct tracecmd_output *handle; |
| struct tep_handle *pevent; |
| char buf[BUFSIZ]; |
| int endian4; |
| |
| handle = malloc(sizeof(*handle)); |
| if (!handle) |
| return NULL; |
| memset(handle, 0, sizeof(*handle)); |
| |
| list_head_init(&handle->options); |
| |
| handle->fd = fd; |
| if (tracing_dir) { |
| handle->tracing_dir = strdup(tracing_dir); |
| if (!handle->tracing_dir) |
| goto out_free; |
| } |
| |
| handle->msg_handle = msg_handle; |
| |
| if (select_file_version(handle, ihandle)) |
| goto out_free; |
| |
| buf[0] = 23; |
| buf[1] = 8; |
| buf[2] = 68; |
| memcpy(buf + 3, "tracing", 7); |
| |
| if (do_write_check(handle, buf, 10)) |
| goto out_free; |
| |
| sprintf(buf, "%lu", handle->file_version); |
| if (do_write_check(handle, buf, strlen(buf) + 1)) |
| goto out_free; |
| |
| /* get endian and page size */ |
| if (ihandle) { |
| pevent = tracecmd_get_tep(ihandle); |
| /* Use the pevent of the ihandle for later writes */ |
| handle->pevent = tracecmd_get_tep(ihandle); |
| tep_ref(pevent); |
| if (tep_is_file_bigendian(pevent)) |
| buf[0] = 1; |
| else |
| buf[0] = 0; |
| handle->page_size = tracecmd_page_size(ihandle); |
| } else { |
| if (tracecmd_host_bigendian()) |
| buf[0] = 1; |
| else |
| buf[0] = 0; |
| handle->page_size = getpagesize(); |
| } |
| |
| if (do_write_check(handle, buf, 1)) |
| goto out_free; |
| |
| /* save size of long (this may not be what the kernel is) */ |
| buf[0] = sizeof(long); |
| if (do_write_check(handle, buf, 1)) |
| goto out_free; |
| |
| endian4 = convert_endian_4(handle, handle->page_size); |
| if (do_write_check(handle, &endian4, 4)) |
| goto out_free; |
| handle->file_state = TRACECMD_FILE_INIT; |
| |
| if (ihandle) |
| return handle; |
| |
| if (read_header_files(handle)) |
| goto out_free; |
| |
| if (read_ftrace_files(handle)) |
| goto out_free; |
| |
| if (read_event_files(handle, list)) |
| goto out_free; |
| |
| if (read_proc_kallsyms(handle, kallsyms)) |
| goto out_free; |
| |
| if (read_ftrace_printk(handle)) |
| goto out_free; |
| |
| return handle; |
| |
| out_free: |
| tracecmd_output_close(handle); |
| return NULL; |
| } |
| |
| static struct tracecmd_output *create_file(const char *output_file, |
| struct tracecmd_input *ihandle, |
| const char *tracing_dir, |
| const char *kallsyms, |
| struct tracecmd_event_list *list) |
| { |
| struct tracecmd_output *handle; |
| int fd; |
| |
| fd = open(output_file, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, 0644); |
| if (fd < 0) |
| return NULL; |
| |
| handle = create_file_fd(fd, ihandle, tracing_dir, kallsyms, list, NULL); |
| if (!handle) { |
| close(fd); |
| unlink(output_file); |
| } |
| |
| return handle; |
| } |
| |
| /** |
| * 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 (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 = malloc(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; |
| } |
| cpus = convert_endian_4(handle, cpus); |
| ret = do_write_check(handle, &cpus, 4); |
| if (ret < 0) |
| return ret; |
| handle->file_state = TRACECMD_FILE_CPU_COUNT; |
| return 0; |
| } |
| |
| int tracecmd_write_options(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; |
| |
| 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 = lseek64(handle->fd, 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; |
| } |
| |
| int tracecmd_append_options(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 (lseek64(handle->fd, 0, SEEK_END) == (off_t)-1) |
| return -1; |
| offset = lseek64(handle->fd, -2, SEEK_CUR); |
| if (offset == (off_t)-1) |
| return -1; |
| |
| r = pread(handle->fd, &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 = lseek64(handle->fd, 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; |
| } |
| |
| struct tracecmd_option * |
| tracecmd_add_buffer_option(struct tracecmd_output *handle, const char *name, |
| int cpus) |
| { |
| struct tracecmd_option *option; |
| char *buf; |
| int size = 8 + strlen(name) + 1; |
| |
| buf = malloc(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_write_cmdlines(struct tracecmd_output *handle) |
| { |
| 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; |
| } |
| ret = save_tracing_file_data(handle, "saved_cmdlines"); |
| if (ret < 0) |
| return ret; |
| handle->file_state = TRACECMD_FILE_CMD_LINES; |
| return 0; |
| } |
| |
| struct tracecmd_output *tracecmd_create_file_latency(const char *output_file, int cpus) |
| { |
| struct tracecmd_output *handle; |
| char *path; |
| |
| handle = create_file(output_file, NULL, NULL, NULL, &all_event_list); |
| if (!handle) |
| return NULL; |
| |
| /* |
| * 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_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 (do_write_check(handle, "latency ", 10)) |
| goto out_free; |
| |
| path = get_tracing_file(handle, "trace"); |
| if (!path) |
| goto out_free; |
| |
| copy_file(handle, path); |
| |
| put_tracing_file(path); |
| |
| handle->file_state = TRACECMD_FILE_CPU_LATENCY; |
| |
| 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 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 |
| */ |
| 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; |
| } |
| |
| int tracecmd_write_cpu_data(struct tracecmd_output *handle, |
| int cpus, char * const *cpu_data_files) |
| { |
| off64_t *offsets = NULL; |
| unsigned long long *sizes = NULL; |
| off64_t offset; |
| unsigned long long endian8; |
| char *clock = NULL; |
| off64_t check_size; |
| char *file; |
| struct stat st; |
| 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 (do_write_check(handle, "flyrecord", 10)) |
| goto out_free; |
| |
| offsets = malloc(sizeof(*offsets) * cpus); |
| if (!offsets) |
| goto out_free; |
| sizes = malloc(sizeof(*sizes) * cpus); |
| if (!sizes) |
| goto out_free; |
| |
| offset = lseek64(handle->fd, 0, SEEK_CUR); |
| |
| /* hold any extra data for data */ |
| offset += cpus * (16); |
| |
| /* |
| * Unfortunately, the trace_clock data was placed after the |
| * cpu data, and wasn't accounted for with the offsets. |
| * We need to save room for the trace_clock file. This means |
| * we need to find the size of it before we define the final |
| * offsets. |
| */ |
| clock = get_clock(handle); |
| if (!clock) |
| goto out_free; |
| /* Save room for storing the size */ |
| offset += 8; |
| offset += strlen(clock); |
| /* 2 bytes for [] around the clock */ |
| offset += 2; |
| |
| /* Page align offset */ |
| offset = (offset + (handle->page_size - 1)) & ~(handle->page_size - 1); |
| |
| for (i = 0; i < cpus; i++) { |
| file = cpu_data_files[i]; |
| ret = stat(file, &st); |
| if (ret < 0) { |
| tracecmd_warning("can not stat '%s'", file); |
| goto out_free; |
| } |
| offsets[i] = offset; |
| sizes[i] = st.st_size; |
| offset += st.st_size; |
| offset = (offset + (handle->page_size - 1)) & ~(handle->page_size - 1); |
| |
| endian8 = convert_endian_8(handle, offsets[i]); |
| if (do_write_check(handle, &endian8, 8)) |
| goto out_free; |
| endian8 = convert_endian_8(handle, sizes[i]); |
| if (do_write_check(handle, &endian8, 8)) |
| goto out_free; |
| } |
| |
| if (save_clock(handle, clock)) |
| goto out_free; |
| |
| for (i = 0; i < cpus; i++) { |
| if (!tracecmd_get_quiet(handle)) |
| fprintf(stderr, "CPU%d data recorded at offset=0x%llx\n", |
| i, (unsigned long long) offsets[i]); |
| offset = lseek64(handle->fd, offsets[i], SEEK_SET); |
| if (offset == (off64_t)-1) { |
| tracecmd_warning("could not seek to %lld\n", offsets[i]); |
| goto out_free; |
| } |
| check_size = copy_file(handle, cpu_data_files[i]); |
| if (check_size != sizes[i]) { |
| errno = EINVAL; |
| tracecmd_warning("did not match size of %lld to %lld", |
| check_size, sizes[i]); |
| goto out_free; |
| } |
| if (!tracecmd_get_quiet(handle)) |
| fprintf(stderr, " %llu bytes in size\n", |
| (unsigned long long)check_size); |
| } |
| |
| free(offsets); |
| free(sizes); |
| |
| handle->file_state = TRACECMD_FILE_CPU_FLYRECORD; |
| |
| return 0; |
| |
| out_free: |
| free(offsets); |
| free(sizes); |
| return -1; |
| } |
| |
| 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_options(handle); |
| if (ret) |
| return ret; |
| |
| return tracecmd_write_cpu_data(handle, cpus, cpu_data_files); |
| } |
| |
| int tracecmd_append_buffer_cpu_data(struct tracecmd_output *handle, |
| struct tracecmd_option *option, |
| int cpus, char * const *cpu_data_files) |
| { |
| tsize_t offset; |
| stsize_t ret; |
| |
| offset = lseek64(handle->fd, 0, SEEK_CUR); |
| |
| /* Go to the option data, where will write the offest */ |
| ret = lseek64(handle->fd, option->offset, SEEK_SET); |
| if (ret == (off64_t)-1) { |
| tracecmd_warning("could not seek to %lld\n", option->offset); |
| return -1; |
| } |
| |
| if (do_write_check(handle, &offset, 8)) |
| return -1; |
| |
| /* Go back to end of file */ |
| ret = lseek64(handle->fd, offset, SEEK_SET); |
| if (ret == (off64_t)-1) { |
| tracecmd_warning("could not seek to %lld\n", offset); |
| return -1; |
| } |
| |
| return tracecmd_write_cpu_data(handle, cpus, cpu_data_files); |
| } |
| |
| struct tracecmd_output *tracecmd_get_output_handle_fd(int fd) |
| { |
| struct tracecmd_output *handle = NULL; |
| struct tracecmd_input *ihandle; |
| 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 = tracecmd_get_options_offset(ihandle); |
| list_head_init(&handle->options); |
| |
| tracecmd_close(ihandle); |
| |
| return handle; |
| |
| out_free: |
| tracecmd_close(ihandle); |
| free(handle); |
| return NULL; |
| } |
| |
| struct tracecmd_output *tracecmd_create_init_fd(int fd) |
| { |
| return create_file_fd(fd, NULL, NULL, NULL, &all_event_list, NULL); |
| } |
| |
| struct tracecmd_output * |
| tracecmd_create_init_fd_msg(struct tracecmd_msg_handle *msg_handle, |
| struct tracecmd_event_list *list) |
| { |
| return create_file_fd(msg_handle->fd, NULL, NULL, NULL, list, msg_handle); |
| } |
| |
| struct tracecmd_output * |
| tracecmd_create_init_fd_glob(int fd, struct tracecmd_event_list *list) |
| { |
| return create_file_fd(fd, NULL, NULL, NULL, list, NULL); |
| } |
| |
| struct tracecmd_output * |
| tracecmd_create_init_file_glob(const char *output_file, |
| struct tracecmd_event_list *list) |
| { |
| return create_file(output_file, NULL, NULL, NULL, list); |
| } |
| |
| struct tracecmd_output *tracecmd_create_init_file(const char *output_file) |
| { |
| return create_file(output_file, NULL, NULL, NULL, &all_event_list); |
| } |
| |
| struct tracecmd_output *tracecmd_create_init_file_override(const char *output_file, |
| const char *tracing_dir, |
| const char *kallsyms) |
| { |
| return create_file(output_file, NULL, tracing_dir, kallsyms, &all_event_list); |
| } |
| |
| /** |
| * 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 |
| * |
| * 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) |
| { |
| struct tracecmd_output *handle; |
| |
| handle = create_file(file, ihandle, NULL, NULL, &all_event_list); |
| if (!handle) |
| return NULL; |
| |
| if (tracecmd_copy_headers(ihandle, handle->fd, 0, 0) < 0) |
| goto out_free; |
| |
| handle->file_state = tracecmd_get_file_state(ihandle); |
| |
| /* The file is all ready to have cpu data attached */ |
| return handle; |
| |
| out_free: |
| tracecmd_output_close(handle); |
| return NULL; |
| } |
| |
| __hidden bool check_out_state(struct tracecmd_output *handle, int new_state) |
| { |
| return check_file_state(handle->file_version, handle->file_state, new_state); |
| } |