--- a/src/buffer.c Wed Dec 10 23:27:32 2025 +0100 +++ b/src/buffer.c Thu Dec 11 17:08:17 2025 +0100 @@ -66,21 +66,10 @@ buffer->bytes = space; } buffer->capacity = capacity; + buffer->max_capacity = SIZE_MAX; buffer->size = 0; buffer->pos = 0; - buffer->flush = NULL; - - return 0; -} - -int cxBufferEnableFlushing( - CxBuffer *buffer, - CxBufferFlushConfig config -) { - buffer->flush = cxMallocDefault(sizeof(CxBufferFlushConfig)); - if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE - memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig)); return 0; } @@ -88,7 +77,6 @@ if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { cxFree(buffer->allocator, buffer->bytes); } - cxFreeDefault(buffer->flush); memset(buffer, 0, sizeof(CxBuffer)); } @@ -213,6 +201,9 @@ if (newcap == buffer->capacity) { return 0; } + if (newcap > buffer->max_capacity) { + return -1; + } const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; if (buffer->flags & force_copy_flags) { void *newspace = cxMalloc(buffer->allocator, newcap); @@ -236,35 +227,46 @@ } } -static size_t cx_buffer_calculate_minimum_capacity(size_t mincap) { - unsigned long pagesize = cx_system_page_size(); - // if page size is larger than 64 KB - for some reason - truncate to 64 KB - if (pagesize > 65536) pagesize = 65536; - if (mincap < pagesize) { - // when smaller as one page, map to the next power of two - mincap--; - mincap |= mincap >> 1; - mincap |= mincap >> 2; - mincap |= mincap >> 4; - // last operation only needed for pages larger 4096 bytes - // but if/else would be more expensive than just doing this - mincap |= mincap >> 8; - mincap++; - } else { - // otherwise, map to a multiple of the page size - mincap -= mincap % pagesize; - mincap += pagesize; - // note: if newcap is already page aligned, - // this gives a full additional page (which is good) +int cxBufferMaximumCapacity(CxBuffer *buffer, size_t capacity) { + if (capacity < buffer->capacity) { + return -1; } - return mincap; + buffer->max_capacity = capacity; + return 0; } int cxBufferMinimumCapacity(CxBuffer *buffer, size_t newcap) { if (newcap <= buffer->capacity) { return 0; } - newcap = cx_buffer_calculate_minimum_capacity(newcap); + if (newcap > buffer->max_capacity) { + return -1; + } + if (newcap < buffer->max_capacity) { + unsigned long pagesize = cx_system_page_size(); + // if page size is larger than 64 KB - for some reason - truncate to 64 KB + if (pagesize > 65536) pagesize = 65536; + if (newcap < pagesize) { + // when smaller as one page, map to the next power of two + newcap--; + newcap |= newcap >> 1; + newcap |= newcap >> 2; + newcap |= newcap >> 4; + // last operation only needed for pages larger 4096 bytes + // but if/else would be more expensive than just doing this + newcap |= newcap >> 8; + newcap++; + } else { + // otherwise, map to a multiple of the page size + newcap -= newcap % pagesize; + newcap += pagesize; + // note: if newcap is already page aligned, + // this gives a full additional page (which is good) + } + if (newcap > buffer->max_capacity) { + newcap = buffer->max_capacity; + } + } return cxBufferReserve(buffer, newcap); } @@ -290,54 +292,6 @@ } } -static size_t cx_buffer_flush_helper( - const CxBuffer *buffer, - const unsigned char *src, - size_t size, - size_t nitems -) { - // flush data from an arbitrary source - // does not need to be the buffer's contents - size_t max_items = buffer->flush->blksize / size; - size_t fblocks = 0; - size_t flushed_total = 0; - while (nitems > 0 && fblocks < buffer->flush->blkmax) { - fblocks++; - size_t items = nitems > max_items ? max_items : nitems; - size_t flushed = buffer->flush->wfunc( - src, size, items, buffer->flush->target); - if (flushed > 0) { - flushed_total += flushed; - src += flushed * size; - nitems -= flushed; - } else { - // if no bytes can be flushed out anymore, we give up - break; - } - } - return flushed_total; -} - -static size_t cx_buffer_flush_impl(CxBuffer *buffer, size_t size) { - // flush the current contents of the buffer - unsigned char *space = buffer->bytes; - size_t remaining = buffer->pos / size; - size_t flushed_total = cx_buffer_flush_helper( - buffer, space, size, remaining); - - // shift the buffer left after flushing - // IMPORTANT: up to this point, copy on write must have been - // performed already, because we can't do error handling here - cxBufferShiftLeft(buffer, flushed_total*size); - - return flushed_total; -} - -size_t cxBufferFlush(CxBuffer *buffer) { - if (buffer_copy_on_write(buffer)) return 0; - return cx_buffer_flush_impl(buffer, 1); -} - size_t cxBufferWrite( const void *ptr, size_t size, @@ -355,107 +309,52 @@ return nitems; } - size_t len, total_flushed = 0; -cx_buffer_write_retry: + size_t len; if (cx_szmul(size, nitems, &len)) { errno = EOVERFLOW; - return total_flushed; + return 0; } if (buffer->pos > SIZE_MAX - len) { errno = EOVERFLOW; - return total_flushed; + return 0; } + const size_t required = buffer->pos + len; - size_t required = buffer->pos + len; - bool perform_flush = false; + // check if we need to auto-extend if (required > buffer->capacity) { if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { - if (buffer->flush != NULL) { - size_t newcap = cx_buffer_calculate_minimum_capacity(required); - if (newcap > buffer->flush->threshold) { - newcap = buffer->flush->threshold; - } - if (cxBufferReserve(buffer, newcap)) { - return total_flushed; // LCOV_EXCL_LINE - } - if (required > newcap) { - perform_flush = true; - } - } else { - if (cxBufferMinimumCapacity(buffer, required)) { - return total_flushed; // LCOV_EXCL_LINE - } - } - } else { - if (buffer->flush != NULL) { - perform_flush = true; - } else { - // truncate data, if we can neither extend nor flush - len = buffer->capacity - buffer->pos; - if (size > 1) { - len -= len % size; - } - nitems = len / size; + size_t newcap = required < buffer->max_capacity + ? required : buffer->max_capacity; + if (cxBufferMinimumCapacity(buffer, newcap)) { + return 0; // LCOV_EXCL_LINE } } } + // check again and truncate data if capacity is still not enough + if (required > buffer->capacity) { + len = buffer->capacity - buffer->pos; + if (size > 1) { + len -= len % size; + } + nitems = len / size; + } + // check here and not above because of possible truncation if (len == 0) { - return total_flushed; + return 0; } // check if we need to copy if (buffer_copy_on_write(buffer)) return 0; // perform the operation - if (perform_flush) { - size_t items_flushed; - if (buffer->pos == 0) { - // if we don't have data in the buffer, but are instructed - // to flush, it means that we are supposed to relay the data - items_flushed = cx_buffer_flush_helper(buffer, ptr, size, nitems); - if (items_flushed == 0) { - // we needed to relay data, but could not flush anything - // i.e. we have to give up to avoid endless trying - return 0; - } - nitems -= items_flushed; - total_flushed += items_flushed; - if (nitems > 0) { - ptr = ((unsigned char*)ptr) + items_flushed * size; - goto cx_buffer_write_retry; - } - return total_flushed; - } else { - items_flushed = cx_buffer_flush_impl(buffer, size); - if (items_flushed == 0) { - // flush target is full, let's try to truncate - size_t remaining_space; - if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { - remaining_space = buffer->flush->threshold > buffer->pos - ? buffer->flush->threshold - buffer->pos - : 0; - } else { - remaining_space = buffer->capacity > buffer->pos - ? buffer->capacity - buffer->pos - : 0; - } - nitems = remaining_space / size; - if (nitems == 0) { - return total_flushed; - } - } - goto cx_buffer_write_retry; - } - } else { - memcpy(buffer->bytes + buffer->pos, ptr, len); - buffer->pos += len; - if (buffer->pos > buffer->size) { - buffer->size = buffer->pos; - } - return total_flushed + nitems; + memcpy(buffer->bytes + buffer->pos, ptr, len); + buffer->pos += len; + if (buffer->pos > buffer->size) { + buffer->size = buffer->pos; } + return nitems; } size_t cxBufferAppend(