ucx/buffer.c

changeset 440
7c4b9cba09ca
parent 324
ce13a778654a
--- a/ucx/buffer.c	Sun Jan 05 17:41:39 2025 +0100
+++ b/ucx/buffer.c	Sun Jan 05 22:00:39 2025 +0100
@@ -27,10 +27,21 @@
  */
 
 #include "cx/buffer.h"
-#include "cx/utils.h"
 
 #include <stdio.h>
 #include <string.h>
+#include <errno.h>
+
+static int buffer_copy_on_write(CxBuffer* buffer) {
+    if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0;
+    void *newspace = cxMalloc(buffer->allocator, buffer->capacity);
+    if (NULL == newspace) return -1;
+    memcpy(newspace, buffer->space, buffer->size);
+    buffer->space = newspace;
+    buffer->flags &= ~CX_BUFFER_COPY_ON_WRITE;
+    buffer->flags |= CX_BUFFER_FREE_CONTENTS;
+    return 0;
+}
 
 int cxBufferInit(
         CxBuffer *buffer,
@@ -39,13 +50,18 @@
         const CxAllocator *allocator,
         int flags
 ) {
-    if (allocator == NULL) allocator = cxDefaultAllocator;
+    if (allocator == NULL) {
+        allocator = cxDefaultAllocator;
+    }
+    if (flags & CX_BUFFER_COPY_ON_EXTEND) {
+        flags |= CX_BUFFER_AUTO_EXTEND;
+    }
     buffer->allocator = allocator;
     buffer->flags = flags;
     if (!space) {
         buffer->bytes = cxMalloc(allocator, capacity);
         if (buffer->bytes == NULL) {
-            return 1;
+            return -1; // LCOV_EXCL_LINE
         }
         buffer->flags |= CX_BUFFER_FREE_CONTENTS;
     } else {
@@ -55,19 +71,27 @@
     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;
+}
+
 void cxBufferDestroy(CxBuffer *buffer) {
-    if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) {
+    if (buffer->flags & CX_BUFFER_FREE_CONTENTS) {
         cxFree(buffer->allocator, buffer->bytes);
     }
+    free(buffer->flush);
+    memset(buffer, 0, sizeof(CxBuffer));
 }
 
 CxBuffer *cxBufferCreate(
@@ -76,21 +100,26 @@
         const CxAllocator *allocator,
         int flags
 ) {
+    if (allocator == NULL) {
+        allocator = cxDefaultAllocator;
+    }
     CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer));
     if (buf == NULL) return NULL;
     if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) {
         return buf;
     } else {
+        // LCOV_EXCL_START
         cxFree(allocator, buf);
         return NULL;
+        // LCOV_EXCL_STOP
     }
 }
 
 void cxBufferFree(CxBuffer *buffer) {
-    if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) {
-        cxFree(buffer->allocator, buffer->bytes);
-    }
-    cxFree(buffer->allocator, buffer);
+    if (buffer == NULL) return;
+    const CxAllocator *allocator = buffer->allocator;
+    cxBufferDestroy(buffer);
+    cxFree(allocator, buffer);
 }
 
 int cxBufferSeek(
@@ -117,10 +146,11 @@
     npos += offset;
 
     if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) {
+        errno = EOVERFLOW;
         return -1;
     }
 
-    if (npos >= buffer->size) {
+    if (npos > buffer->size) {
         return -1;
     } else {
         buffer->pos = npos;
@@ -130,7 +160,9 @@
 }
 
 void cxBufferClear(CxBuffer *buffer) {
-    memset(buffer->bytes, 0, buffer->size);
+    if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) {
+        memset(buffer->bytes, 0, buffer->size);
+    }
     buffer->size = 0;
     buffer->pos = 0;
 }
@@ -140,7 +172,7 @@
     buffer->pos = 0;
 }
 
-int cxBufferEof(const CxBuffer *buffer) {
+bool cxBufferEof(const CxBuffer *buffer) {
     return buffer->pos >= buffer->size;
 }
 
@@ -152,48 +184,71 @@
         return 0;
     }
 
-    if (cxReallocate(buffer->allocator,
+    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);
+        if (NULL == newspace) return -1;
+        memcpy(newspace, buffer->space, buffer->size);
+        buffer->space = newspace;
+        buffer->capacity = newcap;
+        buffer->flags &= ~force_copy_flags;
+        buffer->flags |= CX_BUFFER_FREE_CONTENTS;
+        return 0;
+    } else if (cxReallocate(buffer->allocator,
                      (void **) &buffer->bytes, newcap) == 0) {
         buffer->capacity = newcap;
         return 0;
     } else {
-        return -1;
+        return -1; // LCOV_EXCL_LINE
     }
 }
 
-/**
- * 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(
@@ -204,6 +259,7 @@
 ) {
     // optimize for easy case
     if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) {
+        if (buffer_copy_on_write(buffer)) return 0;
         memcpy(buffer->bytes + buffer->pos, ptr, nitems);
         buffer->pos += nitems;
         if (buffer->pos > buffer->size) {
@@ -213,80 +269,69 @@
     }
 
     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) == CX_BUFFER_AUTO_EXTEND && required) {
-            if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) {
+        if (buffer->flags & CX_BUFFER_AUTO_EXTEND) {
+            if (buffer->flush != NULL && required > buffer->flush->threshold) {
                 perform_flush = true;
             } else {
                 if (cxBufferMinimumCapacity(buffer, required)) {
-                    return 0;
+                    return 0; // LCOV_EXCL_LINE
                 }
             }
         } 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)) {
-            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 {
@@ -295,11 +340,24 @@
         if (buffer->pos > buffer->size) {
             buffer->size = buffer->pos;
         }
-        return nitems_out;
+        return nitems;
     }
 
 }
 
+size_t cxBufferAppend(
+        const void *ptr,
+        size_t size,
+        size_t nitems,
+        CxBuffer *buffer
+) {
+    size_t pos = buffer->pos;
+    buffer->pos = buffer->size;
+    size_t written = cxBufferWrite(ptr, size, nitems, buffer);
+    buffer->pos = pos;
+    return written;
+}
+
 int cxBufferPut(
         CxBuffer *buffer,
         int c
@@ -313,6 +371,17 @@
     }
 }
 
+int cxBufferTerminate(CxBuffer *buffer) {
+    bool success = 0 == cxBufferPut(buffer, 0);
+    if (success) {
+        buffer->pos--;
+        buffer->size--;
+        return 0;
+    } else {
+        return -1;
+    }
+}
+
 size_t cxBufferPutString(
         CxBuffer *buffer,
         const char *str
@@ -328,6 +397,7 @@
 ) {
     size_t len;
     if (cx_szmul(size, nitems, &len)) {
+        errno = EOVERFLOW;
         return 0;
     }
     if (buffer->pos + len > buffer->size) {
@@ -362,6 +432,7 @@
     if (shift >= buffer->size) {
         buffer->pos = buffer->size = 0;
     } else {
+        if (buffer_copy_on_write(buffer)) return -1;
         memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift);
         buffer->size -= shift;
 
@@ -378,14 +449,18 @@
         CxBuffer *buffer,
         size_t shift
 ) {
+    if (buffer->size > SIZE_MAX - shift) {
+        errno = EOVERFLOW;
+        return -1;
+    }
     size_t req_capacity = buffer->size + shift;
     size_t movebytes;
 
     // auto extend buffer, if required and enabled
     if (buffer->capacity < req_capacity) {
-        if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND) {
+        if (buffer->flags & CX_BUFFER_AUTO_EXTEND) {
             if (cxBufferMinimumCapacity(buffer, req_capacity)) {
-                return 1;
+                return -1; // LCOV_EXCL_LINE
             }
             movebytes = buffer->size;
         } else {
@@ -395,8 +470,11 @@
         movebytes = buffer->size;
     }
 
-    memmove(buffer->bytes + shift, buffer->bytes, movebytes);
-    buffer->size = shift + movebytes;
+    if (movebytes > 0) {
+        if (buffer_copy_on_write(buffer)) return -1;
+        memmove(buffer->bytes + shift, buffer->bytes, movebytes);
+        buffer->size = shift + movebytes;
+    }
 
     buffer->pos += shift;
     if (buffer->pos > buffer->size) {

mercurial