blob: 6e4549f99e072ea5d438ab0cc25f9e79b9f8210f [file] [log] [blame]
// 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);
}