// SPDX-FileCopyrightText: 2009-2020 ret2libc // SPDX-FileCopyrightText: 2021 Florian Märkl // SPDX-License-Identifier: LGPL-3.0-only #include 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 /**/ 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 assert(newsize); // 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; }