blob: a63295e6237b2521759aeb40a15d88efa8c47519 [file]
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (C) 2021, VMware, Tzvetomir Stoyanov tz.stoyanov@gmail.com>
*
*/
#include <stdlib.h>
#include <sys/time.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include "trace-cmd-private.h"
#include "trace-cmd-local.h"
struct compress_proto {
struct compress_proto *next;
char *proto_name;
char *proto_version;
int weight;
int (*compress_block)(void *ctx, const void *in, int in_bytes, void *out, int out_bytes);
int (*uncompress_block)(void *ctx, const void *in, int in_bytes, void *out, int out_bytes);
unsigned int (*compress_size)(void *ctx, unsigned int bytes);
bool (*is_supported)(const char *name, const char *version);
void *(*new_context)(void);
void (*free_context)(void *ctx);
};
static struct compress_proto *proto_list;
struct tracecmd_compression {
int fd;
unsigned int capacity;
unsigned int capacity_read;
unsigned long pointer;
char *buffer;
struct compress_proto *proto;
struct tep_handle *tep;
struct tracecmd_msg_handle *msg_handle;
void *context;
};
static int read_fd(int fd, char *dst, int len)
{
size_t size = 0;
int r;
do {
r = read(fd, dst+size, len);
if (r > 0) {
size += r;
len -= r;
} else
break;
} while (r > 0);
if (len)
return -1;
return size;
}
static long long write_fd(int fd, const void *data, size_t size)
{
long long tot = 0;
long long w;
do {
w = write(fd, data + tot, size - tot);
tot += w;
if (!w)
break;
if (w < 0)
return w;
} while (tot != size);
return tot;
}
static long long do_write(struct tracecmd_compression *handle,
const void *data, unsigned long long size)
{
int ret;
if (handle->msg_handle) {
ret = tracecmd_msg_data_send(handle->msg_handle, data, size);
if (ret)
return -1;
return size;
}
return write_fd(handle->fd, data, size);
}
static inline int buffer_extend(struct tracecmd_compression *handle, unsigned int size)
{
int extend;
char *buf;
if (size <= handle->capacity)
return 0;
extend = (size / BUFSIZ + 1) * BUFSIZ;
buf = realloc(handle->buffer, extend);
if (!buf)
return -1;
handle->buffer = buf;
handle->capacity = extend;
return 0;
}
/**
* tracecmd_compress_lseek - Move the read/write pointer into the compression buffer
* @handle: compression handle
* @offset: number of bytes to move the pointer, can be negative or positive
* @whence: the starting position of the pointer movement,
*
* Returns the new file pointer on success, or -1 in case of an error.
*/
off64_t tracecmd_compress_lseek(struct tracecmd_compression *handle, off64_t offset, int whence)
{
unsigned long p;
if (!handle || !handle->buffer)
return (off64_t)-1;
switch (whence) {
case SEEK_CUR:
p = handle->pointer + offset;
break;
case SEEK_END:
p = handle->capacity + offset;
break;
case SEEK_SET:
p = offset;
break;
default:
return (off64_t)-1;
}
if (buffer_extend(handle, p))
return (off64_t)-1;
handle->pointer = p;
return p;
}
static int compress_read(struct tracecmd_compression *handle, char *dst, int len)
{
if (handle->pointer > handle->capacity_read)
return -1;
if (handle->pointer + len > handle->capacity_read)
len = handle->capacity_read - handle->pointer;
memcpy(dst, handle->buffer + handle->pointer, len);
return len;
}
/**
* tracecmd_compress_pread - pread() on compression buffer
* @handle: compression handle
* @dst: return, store the read data
* @len: length of data to be read
* @offset: offset in the buffer of data to be read
*
* Read a @len of data from the compression buffer at given @offset,
* without updating the buffer pointer.
*
* On success returns the number of bytes read, or -1 on failure.
*/
int tracecmd_compress_pread(struct tracecmd_compression *handle, char *dst, int len, off_t offset)
{
int ret;
if (!handle || !handle->buffer || offset > handle->capacity_read)
return -1;
ret = tracecmd_compress_lseek(handle, offset, SEEK_SET);
if (ret < 0)
return ret;
return compress_read(handle, dst, len);
}
/**
* tracecmd_compress_buffer_read - read() from compression buffer
* @handle: compression handle
* @dst: return, store the read data
* @len: length of data to be read
*
* Read a @len of data from the compression buffer
*
* On success returns the number of bytes read, or -1 on failure.
*/
int tracecmd_compress_buffer_read(struct tracecmd_compression *handle, char *dst, int len)
{
int ret;
if (!handle || !handle->buffer)
return -1;
ret = compress_read(handle, dst, len);
if (ret > 0)
handle->pointer += ret;
return ret;
}
/**
* tracecmd_compress_reset - Reset the compression buffer
* @handle: compression handle
*
* Reset the compression buffer, any data currently in the buffer
* will be destroyed.
*
*/
void tracecmd_compress_reset(struct tracecmd_compression *handle)
{
if (!handle)
return;
free(handle->buffer);
handle->buffer = NULL;
handle->pointer = 0;
handle->capacity_read = 0;
handle->capacity = 0;
}
/**
* tracecmd_uncompress_block - uncompress a memory block
* @handle: compression handle
*
* Read compressed memory block from the file and uncompress it into
* internal buffer. The tracecmd_compress_buffer_read() can be used
* to read the uncompressed data from the buffer.
*
* Returns 0 on success, or -1 in case of an error.
*/
int tracecmd_uncompress_block(struct tracecmd_compression *handle)
{
unsigned int s_uncompressed;
unsigned int s_compressed;
char *bytes = NULL;
char buf[4];
int size;
int ret;
if (!handle || !handle->proto || !handle->proto->uncompress_block)
return -1;
tracecmd_compress_reset(handle);
if (read(handle->fd, buf, 4) != 4)
return -1;
s_compressed = tep_read_number(handle->tep, buf, 4);
if (read(handle->fd, buf, 4) != 4)
return -1;
s_uncompressed = tep_read_number(handle->tep, buf, 4);
size = s_uncompressed > s_compressed ? s_uncompressed : s_compressed;
handle->buffer = malloc(size);
if (!handle->buffer)
return -1;
bytes = malloc(s_compressed);
if (!bytes)
goto error;
if (read_fd(handle->fd, bytes, s_compressed) < 0)
goto error;
ret = handle->proto->uncompress_block(handle->context,
bytes, s_compressed, handle->buffer, size);
if (ret < 0)
goto error;
free(bytes);
handle->pointer = 0;
handle->capacity_read = ret;
handle->capacity = size;
return 0;
error:
tracecmd_compress_reset(handle);
free(bytes);
return -1;
}
/**
* tracecmd_compress_block - compress a memory block
* @handle: compression handle
*
* Compress the content of the internal memory buffer and write
* the compressed data in the file. The tracecmd_compress_buffer_write()
* can be used to write data into the internal memory buffer,
* before calling this API.
*
* Returns 0 on success, or -1 in case of an error.
*/
int tracecmd_compress_block(struct tracecmd_compression *handle)
{
unsigned int size, real_size;
char *buf;
int endian4;
int ret;
if (!handle || !handle->proto ||
!handle->proto->compress_size || !handle->proto->compress_block)
return -1;
size = handle->proto->compress_size(handle->context, handle->pointer);
buf = malloc(size);
if (!buf)
return -1;
real_size = handle->proto->compress_block(handle->context, handle->buffer, handle->pointer, buf, size);
if (real_size < 0) {
ret = real_size;
goto out;
}
/* Write compressed data size */
endian4 = tep_read_number(handle->tep, &real_size, 4);
ret = do_write(handle, &endian4, 4);
if (ret != 4)
goto out;
/* Write uncompressed data size */
endian4 = tep_read_number(handle->tep, &handle->pointer, 4);
ret = do_write(handle, &endian4, 4);
if (ret != 4) {
ret = -1;
goto out;
}
/* Write compressed data */
ret = do_write(handle, buf, real_size);
if (ret != real_size) {
ret = -1;
goto out;
}
ret = 0;
tracecmd_compress_reset(handle);
out:
free(buf);
return ret;
}
/**
* tracecmd_compress_buffer_write - write() to compression buffer
* @handle: compression handle
* @data: data to be written
* @size: size of @data
*
* Write @data of @size in the compression buffer
*
* Returns 0 on success, or -1 on failure.
*/
int tracecmd_compress_buffer_write(struct tracecmd_compression *handle,
const void *data, unsigned long long size)
{
if (!handle)
return -1;
if (buffer_extend(handle, handle->pointer + size))
return -1;
memcpy(&handle->buffer[handle->pointer], data, size);
handle->pointer += size;
if (handle->capacity_read < handle->pointer)
handle->capacity_read = handle->pointer;
return 0;
}
/**
* tracecmd_compress_init - initialize the library with available compression algorithms
*/
void tracecmd_compress_init(void)
{
struct timeval time;
gettimeofday(&time, NULL);
srand((time.tv_sec * 1000) + (time.tv_usec / 1000));
#ifdef HAVE_ZLIB
tracecmd_zlib_init();
#endif
tracecmd_zstd_init();
}
static struct compress_proto *compress_proto_select(void)
{
struct compress_proto *proto = proto_list;
struct compress_proto *selected = NULL;
while (proto) {
if (!selected || selected->weight > proto->weight)
selected = proto;
proto = proto->next;
}
return selected;
}
/**
* tracecmd_compress_alloc - Allocate a new compression context
* @name: name of the compression algorithm.
* If NULL - auto select the best available algorithm
* @version: version of the compression algorithm, can be NULL
* @fd: file descriptor for reading / writing data
* @tep: tep handle, used to encode the data
* @msg_handle: message handle, use it for reading / writing data instead of @fd
*
* Returns NULL on failure or pointer to allocated compression context.
* The returned context must be freed by tracecmd_compress_destroy()
*/
struct tracecmd_compression *tracecmd_compress_alloc(const char *name, const char *version,
int fd, struct tep_handle *tep,
struct tracecmd_msg_handle *msg_handle)
{
struct tracecmd_compression *new;
struct compress_proto *proto;
if (name) {
proto = proto_list;
while (proto) {
if (proto->is_supported && proto->is_supported(name, version))
break;
proto = proto->next;
}
} else {
proto = compress_proto_select();
}
if (!proto)
return NULL;
new = calloc(1, sizeof(*new));
if (!new)
return NULL;
new->fd = fd;
new->tep = tep;
new->msg_handle = msg_handle;
new->proto = proto;
if (proto->new_context)
new->context = proto->new_context();
return new;
}
/**
* tracecmd_compress_destroy - Free a compression context
* @handle: handle to the compression context that will be freed
*/
void tracecmd_compress_destroy(struct tracecmd_compression *handle)
{
if (!handle)
return;
tracecmd_compress_reset(handle);
if (handle->proto && handle->proto->free_context)
handle->proto->free_context(handle->context);
free(handle);
}
/**
* tracecmd_compress_is_supported - check if compression algorithm is supported
* @name: name of the compression algorithm.
* @version: version of the compression algorithm.
*
* Checks if compression algorithm with given name and version is supported.
* Returns true if the algorithm is supported or false if it is not.
*/
bool tracecmd_compress_is_supported(const char *name, const char *version)
{
struct compress_proto *proto = proto_list;
if (!name)
return NULL;
while (proto) {
if (proto->is_supported && proto->is_supported(name, version))
return true;
proto = proto->next;
}
return false;
}
/**
* tracecmd_compress_proto_get_name - get name and version of compression algorithm
* @compress: compression handle.
* @name: return, name of the compression algorithm.
* @version: return, version of the compression algorithm.
*
* Returns 0 on success, or -1 in case of an error. If 0 is returned, the name
* and version of the algorithm are stored in @name and @version. The returned
* strings must *not* be freed.
*/
int tracecmd_compress_proto_get_name(struct tracecmd_compression *compress,
const char **name, const char **version)
{
if (!compress || !compress->proto)
return -1;
if (name)
*name = compress->proto->proto_name;
if (version)
*version = compress->proto->proto_version;
return 0;
}
/**
* tracecmd_compress_proto_register - register a new compression algorithm
* @name: name of the compression algorithm.
* @version: version of the compression algorithm.
* @weight: weight of the compression algorithm, lower is better.
* @compress: compression hook, called to compress a memory block.
* @uncompress: uncompression hook, called to uncompress a memory block.
* @compress_size: hook, called to get the required minimum size of the buffer
* for compression given number of bytes.
* @is_supported: check hook, called to check if compression with given name and
* version is supported by this plugin.
*
* Returns 0 on success, or -1 in case of an error. If algorithm with given name
* and version is already registered, -1 is returned.
*/
int tracecmd_compress_proto_register(struct tracecmd_compression_proto *proto)
{
struct compress_proto *new;
if (!proto || !proto->name || !proto->compress || !proto->uncompress)
return -1;
if (tracecmd_compress_is_supported(proto->name, proto->version))
return -1;
new = calloc(1, sizeof(*new));
if (!new)
return -1;
new->proto_name = strdup(proto->name);
if (!new->proto_name)
goto error;
new->proto_version = strdup(proto->version);
if (!new->proto_version)
goto error;
new->compress_block = proto->compress;
new->uncompress_block = proto->uncompress;
new->compress_size = proto->compress_size;
new->is_supported = proto->is_supported;
new->weight = proto->weight;
new->next = proto_list;
new->new_context = proto->new_context;
new->free_context = proto->free_context;
proto_list = new;
return 0;
error:
free(new->proto_name);
free(new->proto_version);
free(new);
return -1;
}
/**
* tracecmd_compress_free - free the library resources, related to available compression algorithms
*
*/
void tracecmd_compress_free(void)
{
struct compress_proto *proto = proto_list;
struct compress_proto *del;
while (proto) {
del = proto;
proto = proto->next;
free(del->proto_name);
free(del->proto_version);
free(del);
}
proto_list = NULL;
}
/**
* tracecmd_compress_protos_get - get a list of all supported compression algorithms and versions
* @names: return, array with names of all supported compression algorithms
* @versions: return, array with versions of all supported compression algorithms
*
* On success, the size of @names and @versions arrays is returned.
* Those arrays are allocated by the API and must be freed with free() by the
* caller. Both arrays are with same size, each name from @names corresponds to
* a version from @versions. The last element in both arrays is a NULL pointer.
* On error -1 is returned and @names and @versions arrays are not allocated.
*/
int tracecmd_compress_protos_get(char ***names, char ***versions)
{
struct compress_proto *proto = proto_list;
char **n = NULL;
char **v = NULL;
int c, i;
for (c = 0; proto; proto = proto->next)
c++;
if (c < 1)
return c;
n = calloc(c + 1, sizeof(char *));
if (!n)
goto error;
v = calloc(c + 1, sizeof(char *));
if (!v)
goto error;
proto = proto_list;
for (i = 0; i < c && proto; i++) {
n[i] = proto->proto_name;
v[i] = proto->proto_version;
proto = proto->next;
}
n[i] = NULL;
v[i] = NULL;
*names = n;
*versions = v;
return c;
error:
free(n);
free(v);
return -1;
}
/**
* tracecmd_compress_copy_from - Copy and compress data from a file
* @handle: compression handle
* @fd: file descriptor to uncompressed data to copy from
* @chunk_size: size of one compression chunk
* @read_size: Pointer to max bytes to read from. The pointer is updated
* with the actual size of compressed data read. If 0 is passed,
* read until the EOF is reached.
* @write_size: return, size of the compressed data written into @handle
*
* This function reads uncompressed data from given @fd, compresses the data
* using the @handle compression context and writes the compressed data into the
* fd associated with the @handle. The data is compressed on chunks with given
* @chunk_size size. The compressed data is written in the format:
* - 4 bytes, chunks count
* - for each chunk:
* - 4 bytes, size of compressed data in this chunk
* - 4 bytes, uncompressed size of the data in this chunk
* - data, bytes of <size of compressed data in this chunk>
*
* On success 0 is returned, @read_size and @write_size are updated with the size of
* read and written data.
*/
int tracecmd_compress_copy_from(struct tracecmd_compression *handle, int fd, int chunk_size,
unsigned long long *read_size, unsigned long long *write_size)
{
unsigned int rchunk = 0;
unsigned int chunks = 0;
unsigned int wsize = 0;
unsigned int rsize = 0;
unsigned int rmax = 0;
unsigned int csize;
unsigned int size;
unsigned int all;
unsigned int r;
off64_t offset;
char *buf_from;
char *buf_to;
int endian4;
int ret;
if (!handle || !handle->proto ||
!handle->proto->compress_block || !handle->proto->compress_size)
return 0;
if (read_size)
rmax = *read_size;
csize = handle->proto->compress_size(handle->context, chunk_size);
buf_from = malloc(chunk_size);
if (!buf_from)
return -1;
buf_to = malloc(csize);
if (!buf_to)
return -1;
/* save the initial offset and write 0 as initial chunk count */
offset = lseek64(handle->fd, 0, SEEK_CUR);
write_fd(handle->fd, &chunks, 4);
do {
all = 0;
if (rmax > 0 && (rmax - rsize) < chunk_size)
rchunk = (rmax - rsize);
else
rchunk = chunk_size;
do {
r = read(fd, buf_from + all, rchunk - all);
all += r;
if (r <= 0)
break;
} while (all != rchunk);
if (r < 0 || (rmax > 0 && rsize >= rmax))
break;
rsize += all;
size = csize;
if (all > 0) {
ret = handle->proto->compress_block(handle->context,
buf_from, all, buf_to, size);
if (ret < 0) {
if (errno == EINTR)
continue;
break;
}
size = ret;
/* Write compressed data size */
endian4 = tep_read_number(handle->tep, &size, 4);
ret = write_fd(handle->fd, &endian4, 4);
if (ret != 4)
break;
/* Write uncompressed data size */
endian4 = tep_read_number(handle->tep, &all, 4);
ret = write_fd(handle->fd, &endian4, 4);
if (ret != 4)
break;
/* Write the compressed data */
ret = write_fd(handle->fd, buf_to, size);
if (ret != size)
break;
/* data + compress header */
wsize += (size + 8);
chunks++;
}
} while (all > 0);
free(buf_from);
free(buf_to);
if (all)
return -1;
if (lseek64(handle->fd, offset, SEEK_SET) == (off_t)-1)
return -1;
endian4 = tep_read_number(handle->tep, &chunks, 4);
/* write chunks count*/
write_fd(handle->fd, &chunks, 4);
if (lseek64(handle->fd, 0, SEEK_END) == (off_t)-1)
return -1;
if (read_size)
*read_size = rsize;
if (write_size)
*write_size = wsize;
return 0;
}
/**
* tracecmd_load_chunks_info - Read compression chunks information from the file
* @handle: compression handle
* @chunks_info: return, array with compression chunks information
*
* This function reads information of all compression chunks in the current
* compression block from the file and fills that information in a newly
* allocated array @chunks_info which is returned.
*
* On success count of compression chunks is returned. Array of that count is
* allocated and returned in @chunks_info. Each entry describes one compression
* chunk. On error -1 is returned. In case of success, @chunks_info must be
* freed by free().
*/
int tracecmd_load_chunks_info(struct tracecmd_compression *handle,
struct tracecmd_compress_chunk **chunks_info)
{
struct tracecmd_compress_chunk *chunks = NULL;
unsigned long long size = 0;
unsigned int count = 0;
off64_t offset;
int ret = -1;
char buf[4];
int i;
if (!handle)
return -1;
offset = lseek64(handle->fd, 0, SEEK_CUR);
if (offset == (off64_t)-1)
return -1;
if (read(handle->fd, buf, 4) != 4)
return -1;
count = tep_read_number(handle->tep, buf, 4);
if (!count) {
ret = 0;
goto out;
}
chunks = calloc(count, sizeof(struct tracecmd_compress_chunk));
if (!chunks)
goto out;
for (i = 0; i < count; i++) {
chunks[i].zoffset = lseek64(handle->fd, 0, SEEK_CUR);
if (chunks[i].zoffset == (off_t)-1)
goto out;
if (read(handle->fd, buf, 4) != 4)
goto out;
chunks[i].zsize = tep_read_number(handle->tep, buf, 4);
chunks[i].offset = size;
if (read(handle->fd, buf, 4) != 4)
goto out;
chunks[i].size = tep_read_number(handle->tep, buf, 4);
size += chunks[i].size;
if (lseek64(handle->fd, chunks[i].zsize, SEEK_CUR) == (off64_t)-1)
goto out;
}
ret = count;
out:
if (lseek64(handle->fd, offset, SEEK_SET) == (off64_t)-1)
ret = -1;
if (ret > 0 && chunks_info)
*chunks_info = chunks;
else
free(chunks);
return ret;
}
/**
* tracecmd_uncompress_chunk - Uncompress given compression chunk.
* @handle: compression handle
* @chunk: chunk, that will be uncompressed in @data
* @data: Preallocated memory for uncompressed data. Must have enough space
* to hold the uncompressed data.
*
* This function uncompresses the chunk described by @chunk and stores
* the uncompressed data in the preallocated memory @data.
*
* On success 0 is returned and the uncompressed data is stored in @data.
* On error -1 is returned.
*/
int tracecmd_uncompress_chunk(struct tracecmd_compression *handle,
struct tracecmd_compress_chunk *chunk, char *data)
{
char *bytes_in = NULL;
int ret = -1;
if (!handle || !handle->proto || !handle->proto->uncompress_block || !chunk || !data)
return -1;
if (lseek64(handle->fd, chunk->zoffset + 8, SEEK_SET) == (off_t)-1)
return -1;
bytes_in = malloc(chunk->zsize);
if (!bytes_in)
return -1;
if (read_fd(handle->fd, bytes_in, chunk->zsize) < 0)
goto out;
if (handle->proto->uncompress_block(handle->context,
bytes_in, chunk->zsize, data, chunk->size) < 0)
goto out;
ret = 0;
out:
free(bytes_in);
return ret;
}
/**
* tracecmd_uncompress_copy_to - Uncompress data and copy to a file
* @handle: compression handle
* @fd: file descriptor to uncompressed data to copy into
* @read_size: return, size of the compressed data read from @handle
* @write_size: return, size of the uncompressed data written into @fd
*
* This function reads compressed data from the fd, associated with @handle,
* uncompresses it using the @handle compression context and writes
* the uncompressed data into the fd. The compressed data must be in the format:
* - 4 bytes, chunks count
* - for each chunk:
* - 4 bytes, size of compressed data in this chunk
* - 4 bytes, uncompressed size of the data in this chunk
* - data, bytes of <size of compressed data in this chunk>
*
* On success 0 is returned, @read_size and @write_size are updated with
* the size of read and written data.
*/
int tracecmd_uncompress_copy_to(struct tracecmd_compression *handle, int fd,
unsigned long long *read_size, unsigned long long *write_size)
{
unsigned int s_uncompressed;
unsigned int s_compressed;
unsigned int rsize = 0;
unsigned int wsize = 0;
char *bytes_out = NULL;
char *bytes_in = NULL;
int size_out = 0;
int size_in = 0;
int chunks;
char buf[4];
char *tmp;
int ret;
if (!handle || !handle->proto || !handle->proto->uncompress_block)
return -1;
if (read(handle->fd, buf, 4) != 4)
return -1;
chunks = tep_read_number(handle->tep, buf, 4);
rsize += 4;
while (chunks) {
if (read(handle->fd, buf, 4) != 4)
break;
s_compressed = tep_read_number(handle->tep, buf, 4);
rsize += 4;
if (read(handle->fd, buf, 4) != 4)
break;
s_uncompressed = tep_read_number(handle->tep, buf, 4);
rsize += 4;
if (!bytes_in || size_in < s_compressed) {
tmp = realloc(bytes_in, s_compressed);
if (!tmp)
break;
bytes_in = tmp;
size_in = s_compressed;
}
if (!bytes_out || size_out < s_uncompressed) {
tmp = realloc(bytes_out, s_uncompressed);
if (!tmp)
break;
bytes_out = tmp;
size_out = s_uncompressed;
}
if (read_fd(handle->fd, bytes_in, s_compressed) < 0)
break;
rsize += s_compressed;
ret = handle->proto->uncompress_block(handle->context, bytes_in, s_compressed,
bytes_out, s_uncompressed);
if (ret < 0)
break;
write_fd(fd, bytes_out, ret);
wsize += ret;
chunks--;
}
free(bytes_in);
free(bytes_out);
if (chunks)
return -1;
if (read_size)
*read_size = rsize;
if (write_size)
*write_size = wsize;
return 0;
}