rizin/librz/util/buf_sparse.c
2023-09-08 10:27:07 +08:00

354 lines
10 KiB
C

// SPDX-FileCopyrightText: 2009-2020 ret2libc <sirmy15@gmail.com>
// SPDX-FileCopyrightText: 2021 Florian Märkl <info@florianmaerkl.de>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_util.h>
typedef struct sparse_init_config_t {
RzBuffer *base;
RzBufferSparseWriteMode write_mode;
} SparseInitConfig;
typedef struct buf_sparse_priv {
RzBuffer *base; ///< If not NULL, unpopulated bytes are taken from this, else Oxff
RzVector /*<RzBufferSparseChunk>*/ chunks; ///< of RzBufferSparseChunk, non-overlapping, ordered by from addr
ut64 offset;
RzBufferSparseWriteMode write_mode;
} SparsePriv;
static void chunk_fini(void *a, void *user) {
RzBufferSparseChunk *s = (RzBufferSparseChunk *)a;
free(s->data);
}
static bool sparse_limits(SparsePriv *priv, ut64 *max) {
if (rz_vector_empty(&priv->chunks)) {
return false;
}
RzBufferSparseChunk *c = rz_vector_index_ptr(&priv->chunks, rz_vector_len(&priv->chunks) - 1);
*max = c->to + 1;
return true;
}
static int chunk_cmp(ut64 addr, void *a) {
RzBufferSparseChunk *c = a;
return RZ_NUM_CMP(addr, c->from);
}
/**
* \return index of the chunk AFTER the one that might contain the addr (so 0 means there is none)
*/
static size_t chunk_index_in(SparsePriv *priv, ut64 addr) {
size_t i;
rz_vector_upper_bound(&priv->chunks, addr, i, chunk_cmp);
return i;
}
/**
* \return -1 if failed; # of bytes copied if success
*/
static st64 sparse_write(SparsePriv *priv, ut64 addr, const ut8 *data, ut64 len) {
if (!len) {
return -1;
}
if (addr + len < addr) {
// clamp to UT64_MAX (inclusive)
len = 0 - addr;
}
size_t in_start_index = chunk_index_in(priv, addr);
size_t in_end_index = chunk_index_in(priv, addr + len < addr ? UT64_MAX : addr + len);
RzBufferSparseChunk *c = NULL; // the chunk where we will write into
if (in_start_index) {
// if we start writing inside an existing chunk, use it.
c = rz_vector_index_ptr(&priv->chunks, in_start_index - 1);
if (addr > c->to) {
// already after it
c = NULL;
} else {
// inside of it, our start index is the index of this chunk
in_start_index--;
}
}
if (!c) {
c = rz_vector_insert(&priv->chunks, in_start_index, NULL);
if (in_end_index) {
// adjust after insertion
in_end_index++;
}
c->from = addr;
c->to = c->from;
c->data = NULL;
}
// (re)alloc buffer and fill with appropriate data
ut64 newto = addr + len - 1;
RzBufferSparseChunk *in_end_chunk = NULL;
if (in_end_index) {
in_end_chunk = rz_vector_index_ptr(&priv->chunks, in_end_index - 1);
if (in_end_chunk->to > newto) {
newto = in_end_chunk->to;
} else {
// completely swallowed this chunk, nothing to copy
in_end_chunk = NULL;
}
}
ut8 *newbuf = realloc(c->data, newto - c->from + 1);
if (!newbuf) {
return -1;
}
c->data = newbuf;
c->to = newto;
memcpy(c->data + (addr - c->from), data, len);
if (in_end_chunk && in_end_chunk != c) {
memcpy(c->data + (addr - c->from) + len,
in_end_chunk->data + (addr + len - in_end_chunk->from),
in_end_chunk->to - (addr + len) + 1);
}
// remove all chunks that are now overlapped and overwritten
if (in_end_index && in_start_index < in_end_index - 1) {
// remove_range does not free by design
for (size_t i = in_start_index + 1; i <= in_end_index - 1; i++) {
chunk_fini(rz_vector_index_ptr(&priv->chunks, i), NULL);
}
rz_vector_remove_range(&priv->chunks, in_start_index + 1, in_end_index - (in_start_index + 1), NULL);
}
return len;
}
static inline struct buf_sparse_priv *get_priv_sparse(RzBuffer *b) {
struct buf_sparse_priv *priv = (struct buf_sparse_priv *)b->priv;
rz_warn_if_fail(priv);
return priv;
}
static bool buf_sparse_init(RzBuffer *b, const void *user) {
SparsePriv *priv = RZ_NEW0(struct buf_sparse_priv);
if (!priv) {
return false;
}
if (user) {
SparseInitConfig *cfg = (void *)user;
priv->base = cfg->base;
if (priv->base) {
rz_buf_ref(priv->base);
}
priv->write_mode = cfg->write_mode;
} else {
priv->write_mode = RZ_BUF_SPARSE_WRITE_MODE_SPARSE;
}
rz_vector_init(&priv->chunks, sizeof(RzBufferSparseChunk), chunk_fini, NULL);
priv->offset = 0;
b->priv = priv;
return true;
}
static bool buf_sparse_fini(RzBuffer *b) {
struct buf_sparse_priv *priv = get_priv_sparse(b);
rz_vector_fini(&priv->chunks);
rz_buf_free(priv->base);
RZ_FREE(b->priv);
return true;
}
static bool buf_sparse_resize(RzBuffer *b, ut64 newsize) {
SparsePriv *priv = get_priv_sparse(b);
size_t n;
rz_vector_lower_bound(&priv->chunks, newsize, n, chunk_cmp);
// now n == index of the first chunk to be thrown away entirely
if (n < rz_vector_len(&priv->chunks)) {
// remove all excessive chunks if shrinking
for (size_t i = n; i < rz_vector_len(&priv->chunks); i++) {
chunk_fini(rz_vector_index_ptr(&priv->chunks, i), NULL);
}
rz_vector_remove_range(&priv->chunks, n, rz_vector_len(&priv->chunks) - n, NULL);
}
// now n == rz_vector_len(&priv->chunks)
bool must_extend = false; // whether we must add another artificial chunk to reach exactly the size
if (n) {
RzBufferSparseChunk *c = rz_vector_index_ptr(&priv->chunks, n - 1);
if (newsize <= c->to) {
// must chop the now-last block
rz_return_val_if_fail(newsize, false); // newsize > 0 is guaranteed when n > 0, otherwise the lower bound above would have returned 0.
c->to = newsize - 1;
ut8 *tmp = realloc(c->data, c->to - c->from + 1);
if (tmp) {
c->data = tmp;
}
} else {
must_extend = newsize && c->to < newsize - 1;
}
} else {
must_extend = !!newsize;
}
if (must_extend) {
// if necessary, add a byte to reach exactly the desired size
return !!sparse_write(priv, newsize - 1, &b->Oxff_priv, 1);
}
return true;
}
static ut64 buf_sparse_size(RzBuffer *b) {
SparsePriv *priv = get_priv_sparse(b);
ut64 max;
ut64 r = sparse_limits(priv, &max) ? max : 0;
if (priv->base) {
ut64 base_sz = rz_buf_size(priv->base);
if (base_sz > r) {
r = base_sz;
}
}
return r;
}
static st64 buf_sparse_read(RzBuffer *b, ut8 *buf, ut64 len) {
if (!len) {
return 0;
}
SparsePriv *priv = get_priv_sparse(b);
ut64 max = priv->offset + len - 1;
if (max < priv->offset) {
max = UT64_MAX;
len = max - priv->offset + 1;
}
// first inside-chunk is special because we might start inside of it
size_t r = 0;
size_t i = chunk_index_in(priv, priv->offset);
if (i) {
RzBufferSparseChunk *c = rz_vector_index_ptr(&priv->chunks, i - 1);
if (priv->offset <= c->to) {
ut64 to = RZ_MIN(c->to, max);
ut64 rsz = to - priv->offset + 1;
memcpy(buf, c->data + (priv->offset - c->from), rsz);
priv->offset += rsz;
buf += rsz;
r += rsz;
}
}
// non-chunk/chunk alternating
while (priv->offset <= max) {
// in each iteration, write one part like [0xff, 0xff, 0xff][some chunk]
ut64 empty_to = max; // inclusive offset to which to fill with 0xff
ut64 next_off = empty_to + 1; // offset to start at in the next iteration
if (i < rz_vector_len(&priv->chunks)) {
RzBufferSparseChunk *c = rz_vector_index_ptr(&priv->chunks, i);
if (c->from <= empty_to) {
next_off = RZ_MIN(c->to + 1, next_off);
empty_to = c->from - 1;
memcpy(buf + empty_to - priv->offset + 1, c->data, next_off - empty_to - 1);
r += next_off - priv->offset;
}
i++;
}
if (empty_to >= priv->offset) {
// fill non-chunk part with 0xff or base file
if (priv->base) {
rz_buf_read_at(priv->base, priv->offset, buf, empty_to - priv->offset + 1);
} else {
memset(buf, b->Oxff_priv, empty_to - priv->offset + 1);
}
}
buf += next_off - priv->offset;
priv->offset = next_off;
}
return priv->base ? len : r; // if there is a base file, read always fills the entire buffer (to keep the 0xff of the base)
}
static st64 buf_sparse_write(RzBuffer *b, const ut8 *buf, ut64 len) {
SparsePriv *priv = get_priv_sparse(b);
st64 r = -1;
switch (priv->write_mode) {
case RZ_BUF_SPARSE_WRITE_MODE_SPARSE:
r = sparse_write(priv, priv->offset, buf, len);
break;
case RZ_BUF_SPARSE_WRITE_MODE_THROUGH:
if (!priv->base) {
break;
}
r = rz_buf_write_at(priv->base, priv->offset, buf, len);
break;
}
if (r >= 0) {
priv->offset += r;
}
return r;
}
static st64 buf_sparse_seek(RzBuffer *b, st64 addr, int whence) {
struct buf_sparse_priv *priv = get_priv_sparse(b);
ut64 max;
if (addr < 0 && (-addr) > (st64)priv->offset) {
return -1;
}
switch (whence) {
case RZ_BUF_CUR:
priv->offset += addr;
break;
case RZ_BUF_SET:
priv->offset = addr;
break;
case RZ_BUF_END:
if (!sparse_limits(priv, &max)) {
max = 0;
}
priv->offset = max + addr;
break;
default:
rz_warn_if_reached();
return -1;
}
return priv->offset;
}
static const RzBufferMethods buffer_sparse_methods = {
.init = buf_sparse_init,
.fini = buf_sparse_fini,
.read = buf_sparse_read,
.write = buf_sparse_write,
.get_size = buf_sparse_size,
.resize = buf_sparse_resize,
.seek = buf_sparse_seek
};
/// Only for sparse RzBuffers, get all sparse data chunks currently populated.
RZ_API const RzBufferSparseChunk *rz_buf_sparse_get_chunks(RzBuffer *b, RZ_NONNULL size_t *count) {
rz_return_val_if_fail(b && count, NULL);
if (b->methods != &buffer_sparse_methods) {
*count = 0;
return NULL;
}
SparsePriv *priv = get_priv_sparse(b);
*count = rz_vector_len(&priv->chunks);
return rz_vector_index_ptr(&priv->chunks, 0);
}
/// Only for sparse RzBuffers
RZ_API void rz_buf_sparse_set_write_mode(RzBuffer *b, RzBufferSparseWriteMode mode) {
rz_return_if_fail(b);
if (b->methods != &buffer_sparse_methods) {
return;
}
SparsePriv *priv = get_priv_sparse(b);
priv->write_mode = mode;
}
/**
* \param from inclusive
* \param to inclusive
* \return whether the given interval contains chunks populated in the sparse buffer
*/
RZ_API bool rz_buf_sparse_populated_in(RzBuffer *b, ut64 from, ut64 to) {
rz_return_val_if_fail(b, false);
if (b->methods != &buffer_sparse_methods) {
return false;
}
SparsePriv *priv = get_priv_sparse(b);
size_t from_i = chunk_index_in(priv, from);
if (from_i) {
RzBufferSparseChunk *c = rz_vector_index_ptr(&priv->chunks, from_i - 1);
if (from <= c->to) {
return true;
}
}
size_t to_i = chunk_index_in(priv, to);
return to_i > from_i;
}