--- a/ucx/cx/buffer.h Fri Jan 03 21:40:57 2025 +0100 +++ b/ucx/cx/buffer.h Sat Jan 04 13:03:01 2025 +0100 @@ -49,7 +49,7 @@ #include "common.h" #include "allocator.h" -#ifdef __cplusplus +#ifdef __cplusplus extern "C" { #endif @@ -60,14 +60,34 @@ /** * If this flag is enabled, the buffer will automatically free its contents when destroyed. + * + * Do NOT set this flag together with #CX_BUFFER_COPY_ON_WRITE. It will be automatically + * set when the copy-on-write operations is performed. */ #define CX_BUFFER_FREE_CONTENTS 0x01 /** - * If this flag is enabled, the buffer will automatically extends its capacity. + * If this flag is enabled, the buffer will automatically extend its capacity. */ #define CX_BUFFER_AUTO_EXTEND 0x02 +/** + * If this flag is enabled, the buffer will allocate new memory when written to. + * + * The current contents of the buffer will be copied to the new memory and the flag + * will be cleared while the #CX_BUFFER_FREE_CONTENTS flag will be set automatically. + */ +#define CX_BUFFER_COPY_ON_WRITE 0x04 + +/** + * If this flag is enabled, the buffer will copy its contents to a new memory area on reallocation. + * + * After performing the copy, the flag is automatically cleared. + * This flag has no effect on buffers which do not have #CX_BUFFER_AUTO_EXTEND set, which is why + * buffers automatically admit the auto-extend flag when initialized with copy-on-extend enabled. + */ +#define CX_BUFFER_COPY_ON_EXTEND 0x08 + /** Structure for the UCX buffer data. */ typedef struct { /** A pointer to the buffer contents. */ @@ -82,7 +102,7 @@ unsigned char *bytes; }; /** The allocator to use for automatic memory management. */ - CxAllocator const *allocator; + const CxAllocator *allocator; /** Current position of the buffer. */ size_t pos; /** Current capacity (i.e. maximum size) of the buffer. */ @@ -128,6 +148,7 @@ * @see #CX_BUFFER_DEFAULT * @see #CX_BUFFER_FREE_CONTENTS * @see #CX_BUFFER_AUTO_EXTEND + * @see #CX_BUFFER_COPY_ON_WRITE */ int flags; } cx_buffer_s; @@ -140,47 +161,40 @@ /** * Initializes a fresh buffer. * + * You may also provide a read-only \p space, in which case + * you will need to cast the pointer, and you should set the + * #CX_BUFFER_COPY_ON_WRITE flag. + * + * You need to set the size manually after initialization, if + * you provide \p space which already contains data. + * + * When you specify stack memory as \p space and decide to use + * the auto-extension feature, you \em must use the + * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the + * #CX_BUFFER_AUTO_EXTEND flag. + * * \note You may provide \c NULL as argument for \p space. * Then this function will allocate the space and enforce - * the #CX_BUFFER_FREE_CONTENTS flag. + * the #CX_BUFFER_FREE_CONTENTS flag. In that case, specifying + * copy-on-write should be avoided, because the allocated + * space will be leaking after the copy-on-write operation. * * @param buffer the buffer to initialize * @param space pointer to the memory area, or \c NULL to allocate * new memory * @param capacity the capacity of the buffer * @param allocator the allocator this buffer shall use for automatic - * memory management. If \c NULL, the default heap allocator will be used. + * memory management + * (if \c NULL, a default stdlib allocator will be used) * @param flags buffer features (see cx_buffer_s.flags) * @return zero on success, non-zero if a required allocation failed */ -__attribute__((__nonnull__(1))) +cx_attr_nonnull_arg(1) int cxBufferInit( CxBuffer *buffer, void *space, size_t capacity, - CxAllocator const *allocator, - int flags -); - -/** - * Allocates and initializes a fresh buffer. - * - * \note You may provide \c NULL as argument for \p space. - * Then this function will allocate the space and enforce - * the #CX_BUFFER_FREE_CONTENTS flag. - * - * @param space pointer to the memory area, or \c NULL to allocate - * new memory - * @param capacity the capacity of the buffer - * @param allocator the allocator to use for allocating the structure and the automatic - * memory management within the buffer. If \c NULL, the default heap allocator will be used. - * @param flags buffer features (see cx_buffer_s.flags) - * @return a pointer to the buffer on success, \c NULL if a required allocation failed - */ -CxBuffer *cxBufferCreate( - void *space, - size_t capacity, - CxAllocator const *allocator, + const CxAllocator *allocator, int flags ); @@ -193,7 +207,7 @@ * @param buffer the buffer which contents shall be destroyed * @see cxBufferInit() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxBufferDestroy(CxBuffer *buffer); /** @@ -202,13 +216,49 @@ * If the #CX_BUFFER_FREE_CONTENTS feature is enabled, this function also destroys * the contents. If you \em only want to destroy the contents, use cxBufferDestroy(). * + * \remark As with all free() functions, this accepts \c NULL arguments in which + * case it does nothing. + * * @param buffer the buffer to deallocate * @see cxBufferCreate() */ -__attribute__((__nonnull__)) void cxBufferFree(CxBuffer *buffer); /** + * Allocates and initializes a fresh buffer. + * + * You may also provide a read-only \p space, in which case + * you will need to cast the pointer, and you should set the + * #CX_BUFFER_COPY_ON_WRITE flag. + * When you specify stack memory as \p space and decide to use + * the auto-extension feature, you \em must use the + * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the + * #CX_BUFFER_AUTO_EXTEND flag. + * + * \note You may provide \c NULL as argument for \p space. + * Then this function will allocate the space and enforce + * the #CX_BUFFER_FREE_CONTENTS flag. + * + * @param space pointer to the memory area, or \c NULL to allocate + * new memory + * @param capacity the capacity of the buffer + * @param allocator the allocator to use for allocating the structure and the automatic + * memory management within the buffer + * (if \c NULL, a default stdlib allocator will be used) + * @param flags buffer features (see cx_buffer_s.flags) + * @return a pointer to the buffer on success, \c NULL if a required allocation failed + */ +cx_attr_malloc +cx_attr_dealloc(cxBufferFree, 1) +cx_attr_nodiscard +CxBuffer *cxBufferCreate( + void *space, + size_t capacity, + const CxAllocator *allocator, + int flags +); + +/** * Shifts the contents of the buffer by the given offset. * * If the offset is positive, the contents are shifted to the right. @@ -239,9 +289,9 @@ * * @param buffer the buffer * @param shift the shift offset (negative means left shift) - * @return 0 on success, non-zero if a required auto-extension fails + * @return 0 on success, non-zero if a required auto-extension or copy-on-write fails */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferShift( CxBuffer *buffer, off_t shift @@ -253,10 +303,10 @@ * * @param buffer the buffer * @param shift the shift offset - * @return 0 on success, non-zero if a required auto-extension fails + * @return 0 on success, non-zero if a required auto-extension or copy-on-write fails * @see cxBufferShift() */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferShiftRight( CxBuffer *buffer, size_t shift @@ -266,15 +316,12 @@ * Shifts the buffer to the left. * See cxBufferShift() for details. * - * \note Since a left shift cannot fail due to memory allocation problems, this - * function always returns zero. - * * @param buffer the buffer * @param shift the positive shift offset - * @return always zero + * @return usually zero, except the buffer uses copy-on-write and the allocation fails * @see cxBufferShift() */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferShiftLeft( CxBuffer *buffer, size_t shift @@ -300,7 +347,7 @@ * @return 0 on success, non-zero if the position is invalid * */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferSeek( CxBuffer *buffer, off_t offset, @@ -313,10 +360,13 @@ * The data is deleted by zeroing it with a call to memset(). * If you do not need that, you can use the faster cxBufferReset(). * + * \note If the #CX_BUFFER_COPY_ON_WRITE flag is set, this function + * will not erase the data and behave exactly as cxBufferReset(). + * * @param buffer the buffer to be cleared * @see cxBufferReset() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxBufferClear(CxBuffer *buffer); /** @@ -328,18 +378,19 @@ * @param buffer the buffer to be cleared * @see cxBufferClear() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxBufferReset(CxBuffer *buffer); /** * Tests, if the buffer position has exceeded the buffer size. * * @param buffer the buffer to test - * @return non-zero, if the current buffer position has exceeded the last + * @return true, if the current buffer position has exceeded the last * byte of the buffer's contents. */ -__attribute__((__nonnull__)) -int cxBufferEof(CxBuffer const *buffer); +cx_attr_nonnull +cx_attr_nodiscard +bool cxBufferEof(const CxBuffer *buffer); /** @@ -351,7 +402,7 @@ * @param capacity the minimum required capacity for this buffer * @return 0 on success or a non-zero value on failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferMinimumCapacity( CxBuffer *buffer, size_t capacity @@ -383,9 +434,35 @@ * @param buffer the CxBuffer to write to * @return the total count of elements written */ -__attribute__((__nonnull__)) +cx_attr_nonnull size_t cxBufferWrite( - void const *ptr, + const void *ptr, + size_t size, + size_t nitems, + CxBuffer *buffer +); + +/** + * Appends data to a CxBuffer. + * + * The data is always appended to current data within the buffer, + * regardless of the current position. + * This is especially useful when the buffer is primarily meant for reading + * while additional data is added to the buffer occasionally. + * Consequently, the position of the buffer is unchanged after this operation. + * + * \note The signature is compatible with the fwrite() family of functions. + * + * @param ptr a pointer to the memory area containing the bytes to be written + * @param size the length of one element + * @param nitems the element count + * @param buffer the CxBuffer to write to + * @return the total count of elements written + * @see cxBufferWrite() + */ +cx_attr_nonnull +size_t cxBufferAppend( + const void *ptr, size_t size, size_t nitems, CxBuffer *buffer @@ -404,7 +481,7 @@ * @param buffer the CxBuffer to read from * @return the total number of elements read */ -__attribute__((__nonnull__)) +cx_attr_nonnull size_t cxBufferRead( void *ptr, size_t size, @@ -417,30 +494,46 @@ * * The least significant byte of the argument is written to the buffer. If the * end of the buffer is reached and #CX_BUFFER_AUTO_EXTEND feature is enabled, - * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature is - * disabled or buffer extension fails, \c EOF is returned. + * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature + * is disabled or buffer extension fails, \c EOF is returned. * * On successful write, the position of the buffer is increased. * * @param buffer the buffer to write to * @param c the character to write - * @return the byte that has bean written or \c EOF when the end of the stream is + * @return the byte that has been written or \c EOF when the end of the stream is * reached and automatic extension is not enabled or not possible */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferPut( CxBuffer *buffer, int c ); /** + * Writes a terminating zero to a buffer. + * + * On successful write, \em neither the position \em nor the size of the buffer is + * increased. + * + * The purpose of this function is to have the written data ready to be used as + * a C string. + * + * @param buffer the buffer to write to + * @return zero, if the terminator could be written, non-zero otherwise + */ +cx_attr_nonnull +int cxBufferTerminate(CxBuffer *buffer); + +/** * Writes a string to a buffer. * * @param buffer the buffer * @param str the zero-terminated string * @return the number of bytes written */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) size_t cxBufferPutString( CxBuffer *buffer, const char *str @@ -454,7 +547,7 @@ * @param buffer the buffer to read from * @return the character or \c EOF, if the end of the buffer is reached */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferGet(CxBuffer *buffer); #ifdef __cplusplus