--- a/src/buffer.c Sun Jan 05 14:03:30 2025 +0100 +++ b/src/buffer.c Sun Jan 05 18:19:42 2025 +0100 @@ -71,12 +71,18 @@ buffer->size = 0; buffer->pos = 0; - buffer->flush_func = NULL; - buffer->flush_target = NULL; - buffer->flush_blkmax = 0; - buffer->flush_blksize = 4096; - buffer->flush_threshold = SIZE_MAX; + buffer->flush = NULL; + + return 0; +} +int cxBufferEnableFlushing( + CxBuffer *buffer, + CxBufferFlushConfig config +) { + buffer->flush = malloc(sizeof(CxBufferFlushConfig)); + if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE + memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig)); return 0; } @@ -84,6 +90,7 @@ if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { cxFree(buffer->allocator, buffer->bytes); } + free(buffer->flush); memset(buffer, 0, sizeof(CxBuffer)); } @@ -196,39 +203,52 @@ } } -/** - * Helps flushing data to the flush target of a buffer. - * - * @param buffer the buffer containing the config - * @param space the data to flush - * @param size the element size - * @param nitems the number of items - * @return the number of items flushed - */ -static size_t cx_buffer_write_flush_helper( - CxBuffer *buffer, - const unsigned char *space, +static size_t cx_buffer_flush_helper( + const CxBuffer *buffer, size_t size, + const unsigned char *src, size_t nitems ) { - size_t pos = 0; - size_t remaining = nitems; - size_t max_items = buffer->flush_blksize / size; - while (remaining > 0) { - size_t items = remaining > max_items ? max_items : remaining; - size_t flushed = buffer->flush_func( - space + pos, - size, items, - buffer->flush_target); + // 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) { - pos += (flushed * size); - remaining -= flushed; + flushed_total += flushed; + src += flushed * size; + nitems -= flushed; } else { // if no bytes can be flushed out anymore, we give up break; } } - return nitems - remaining; + 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, size, space, 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( @@ -249,20 +269,20 @@ } size_t len; - size_t nitems_out = nitems; if (cx_szmul(size, nitems, &len)) { errno = EOVERFLOW; return 0; } + if (buffer->pos > SIZE_MAX - len) { + errno = EOVERFLOW; + return 0; + } size_t required = buffer->pos + len; - if (buffer->pos > required) { - return 0; - } bool perform_flush = false; if (required > buffer->capacity) { if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { - if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) { + if (buffer->flush != NULL && required > buffer->flush->threshold) { perform_flush = true; } else { if (cxBufferMinimumCapacity(buffer, required)) { @@ -270,71 +290,57 @@ } } } else { - if (buffer->flush_blkmax > 0) { + if (buffer->flush != NULL) { perform_flush = true; } else { - // truncate data to be written, if we can neither extend nor flush + // truncate data, if we can neither extend nor flush len = buffer->capacity - buffer->pos; if (size > 1) { len -= len % size; } - nitems_out = len / size; + nitems = len / size; } } } + // check here and not above because of possible truncation if (len == 0) { - return len; + return 0; } - if (perform_flush) { - size_t flush_max; - if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) { - errno = EOVERFLOW; - return 0; - } - size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL - ? buffer->pos - : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos); - if (flush_pos == buffer->pos) { - // entire buffer has been flushed, we can reset - buffer->size = buffer->pos = 0; - - size_t items_flush; // how many items can also be directly flushed - size_t items_keep; // how many items have to be written to the buffer + // check if we need to copy + if (buffer_copy_on_write(buffer)) return 0; - items_flush = flush_max >= required ? nitems : (flush_max - flush_pos) / size; - if (items_flush > 0) { - items_flush = cx_buffer_write_flush_helper(buffer, ptr, size, items_flush / size); - // in case we could not flush everything, keep the rest + // perform the operation + if (perform_flush) { + size_t items_flush; + 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_flush = cx_buffer_flush_helper(buffer, size, ptr, nitems); + if (items_flush == 0) { + // we needed to flush, but could not flush anything + // give up and avoid endless trying + return 0; } - items_keep = nitems - items_flush; - if (items_keep > 0) { - // try again with the remaining stuff - const unsigned char *new_ptr = ptr; - new_ptr += items_flush * size; - // report the directly flushed items as written plus the remaining stuff - return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer); - } else { - // all items have been flushed - report them as written - return nitems; + size_t ritems = nitems - items_flush; + const unsigned char *rest = ptr; + rest += items_flush * size; + return items_flush + cxBufferWrite(rest, size, ritems, buffer); + } else { + items_flush = cx_buffer_flush_impl(buffer, size); + if (items_flush == 0) { + return 0; } - } else if (flush_pos == 0) { - // nothing could be flushed at all, we immediately give up without writing any data - return 0; - } else { - // we were partially successful, we shift left and try again - cxBufferShiftLeft(buffer, flush_pos); return cxBufferWrite(ptr, size, nitems, buffer); } } else { - if (buffer_copy_on_write(buffer)) return 0; memcpy(buffer->bytes + buffer->pos, ptr, len); buffer->pos += len; if (buffer->pos > buffer->size) { buffer->size = buffer->pos; } - return nitems_out; + return nitems; } }