UNIXworkcode

1 # Buffer 2 3 This buffer implementation can be used to read from or write to memory like you would do with a stream. 4 5 This allows the use of `cx_stream_copy()` (see [](streams.h.md)) to copy contents from one buffer to another, 6 or from a file or network stream to the buffer and vice versa. 7 8 More features for convenient use of the buffer can be enabled, like automatic memory management, 9 automatic resizing of the buffer space, or automatic flushing of contents. 10 11 The functions `cxBufferRead()` and `cxBufferWrite()` are `cx_read_func` and `cx_write_func` compatible, 12 which in turn have a compatible signature to `fread()` and `fwrite()`. 13 However, due to the different pointer type the function pointers do not type check out of the box. 14 For convenience, the macros `cxBufferReadFunc` and `cxBufferWriteFunc` are defined, which perform the necessary cast. 15 16 ## Example 17 18 In the following example we use a `CxBuffer`, the `bprintf()` macro from the [UCX printf header](printf.h.md), 19 and a [UCX map](map.h.md) to create a simple HTTP GET request. 20 21 ```C 22 #include <cx/buffer.h> 23 #include <cx/printf.h> 24 #include <cx/map.h> 25 26 void send_get_request( 27 void *net_target, 28 cx_write_func net_write, 29 const char *host, 30 const char *path, 31 CxMap *header 32 ) { 33 // initialize buffer and use stack memory for small requests 34 char stackmem[128]; 35 CxBuffer buf; 36 cxBufferInit( 37 &buf, stackmem, sizeof(stackmem), 38 cxDefaultAllocator, 39 CX_BUFFER_COPY_ON_EXTEND // move to heap when request is larger 40 ); 41 42 // basic request data 43 cx_bprintf(&buf, "GET %s HTTP/1.1\r\n", path); 44 cx_bprintf(&buf, "Host: %s\r\n", host); 45 46 // add headers 47 CxMapIterator iter = cxMapIterator(header); 48 cx_foreach(CxMapEntry*, entry, iter) { 49 cxstring name = cx_strn(entry->key->data, entry->key->len); 50 cx_bprintf(&buf, "%" CX_PRIstr ": %s\r\n", 51 CX_SFMT(name), entry->value 52 ); 53 } 54 55 // blank line terminates 56 cxBufferWrite("\r\n", 1, 2, &buf); 57 58 // write request to the network stream and destroy the buffer 59 net_write(buf.space, 1, buf.size, net_target); 60 cxBufferDestroy(&buf); 61 } 62 ``` 63 64 ## Create 65 66 ```C 67 #include <cx/buffer.h> 68 69 int cxBufferInit(CxBuffer *buffer, void *space, size_t capacity, 70 const CxAllocator *allocator, int flags); 71 72 CxBuffer *cxBufferCreate(void *space,size_t capacity, 73 const CxAllocator *allocator, int flags); 74 75 // available flags: 76 #define CX_BUFFER_DEFAULT 77 #define CX_BUFFER_FREE_CONTENTS 78 #define CX_BUFFER_AUTO_EXTEND 79 #define CX_BUFFER_COPY_ON_WRITE 80 #define CX_BUFFER_COPY_ON_EXTEND 81 ``` 82 83 For creating a UCX buffer, you have two options: initialize a pre-allocated structure, or allocate and initialize a new structure in one call. 84 85 For the first option, you can call `cxBufferInit()` with the `buffer` argument pointing to an uninitialized `CxBuffer` structure. 86 You can pass a pre-allocated `space`, the desired `capacity`, and an `allocator`. 87 If `space` is `NULL`, the `allocator` is used to allocate enough space to match the desired `capacity`. 88 89 The `flags` argument is a bit-wise-or combination of the constants listed below. 90 91 The function `cxBufferCreate()` is equivalent, except that it uses the `allocator` to allocate a new `CxBuffer` structure, instead of initializing an existing one. 92 93 If any allocation fails, `cxBufferInit()` returns non-zero, or `cxBufferCreate()` returns `NULL`, respectively. 94 95 | Flag | Description | 96 |--------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 97 | CX_BUFFER_DEFAULT | Alias for zero, meaning no flags are set. | 98 | CX_BUFFER_FREE_CONTENTS | When the buffer structure is [destroyed](#destroy), the memory for the contents is also automatically deallocated. | 99 | CX_BUFFER_AUTO_EXTEND | When a write operation would exceed the buffers capacity, the buffer is grown automatically with a call to `cxBufferMinimumCapacity()`. | 100 | CX_BUFFER_COPY_ON_WRITE | Any write operation will result in allocating a copy of the data, first. This is particularly useful, when a buffer is initialized with read-only memory. | 101 | CX_BUFFER_COPY_ON_EXTEND | Only when a write operation would exceed the buffers capacity, the copy-on-write operation is performed. This is useful, when a buffer is initialized with read-write memory which is allocated on the stack. | 102 103 104 ## Destroy 105 106 ```C 107 #include <cx/buffer.h> 108 109 void cxBufferDestroy(CxBuffer *buffer); 110 111 void cxBufferFree(CxBuffer *buffer); 112 ``` 113 114 The above functions destroy the buffer and deallocate the buffer's memory when the `CX_BUFFER_FREE_CONTENTS` flag is set. 115 116 The function `cxBufferDestroy()` is to be used when the buffer was initialized with `cxBufferInit()`, 117 and the function `cxBufferFree()` is to be used when the buffer was created with `cxBufferCreate()`. 118 The only difference is, that `cxBufferFree()` additionally deallocates the memory of the `CxBuffer` structure. 119 120 ## Capacity Management 121 122 ```C 123 #include <cx/buffer.h> 124 125 int cxBufferReserve(CxBuffer *buffer, size_t capacity); 126 127 int cxBufferMinimumCapacity(CxBuffer *buffer, size_t capacity); 128 129 void cxBufferShrink(CxBuffer *buffer, size_t reserve); 130 ``` 131 132 The function `cxBufferMinimumCapacity()` guarantees a buffer capacity of _at least_ `capacity`. 133 It will grow the capacity in powers of two until the system's page size is reached. 134 Then, the new capacity will be a multiple of the page size. 135 The function returns non-zero if increasing the capacity was attempted unsuccessfully. 136 137 The function `cxBufferReserve()`, on the other hand, will reallocate the buffer's space to match exactly the requested `capacity` 138 and the contents are truncated if required. 139 140 You should use `cxBufferReserve()` when you know precisely the required capacity beforehand 141 and `cxBufferMinimumCapacity()` when it is likely that the buffer needs to regrow soon. 142 143 The function `cxBufferShrink()` can be used to shrink the capacity of the buffer to its current size 144 plus a number of `reserve` bytes. 145 If the current capacity is not larger than the size plus the reserve bytes, the function will do nothing. 146 147 > If the buffer is in a copy-on-write state, `cxBufferMinimumCapacity()` will perform the copy-on-write action 148 > before reallocating the space. 149 > The function `cxBufferShrink()` on the other hand does _nothing_ when the buffer is in a copy-on-write state, 150 > because it does not make much sense to copy the memory just to have it in a smaller memory region. 151 152 ## Write 153 154 ```C 155 #include <cx/buffer.h> 156 157 size_t cxBufferWrite(const void *ptr, size_t size, size_t nitems, 158 CxBuffer *buffer); 159 160 int cxBufferPut(CxBuffer *buffer, int c); 161 162 size_t cxBufferPutString(CxBuffer *buffer, const char *str); 163 164 int cxBufferTerminate(CxBuffer *buffer); 165 166 size_t cxBufferAppend(const void *ptr, size_t size, size_t nitems, 167 CxBuffer *buffer); 168 ``` 169 170 The primary function for writing to a buffer is `cxBufferWrite()` 171 which writes up to `nitems` with `size` bytes each from the memory pointed to by `ptr` to the buffer. 172 173 When the capacity of the buffer is not sufficient and the `CX_BUFFER_AUTO_EXTEND` is not set in the buffer, 174 items that do not fit into the buffer are discarded. 175 The function always returns the actual number of items successfully written. 176 This equals the number of bytes if and only if `size=1`. 177 178 The function `cxBufferPut()` is a `putc()`-like wrapper for `cxBufferWrite()` which writes the character `c`, 179 converted to an `unsigned char` to the buffer. 180 Just like `putc()` this function returns the written character on success, and `EOF` on failure, 181 but it does _not_ set `errno` on failure. 182 183 The function `cxBufferPutString()` is a convenience function, 184 that uses stdlib `strlen()` to compute the length of `str` and then invokes `cxBufferWrite()`. 185 186 All the above functions advance the buffer position by the number of bytes written 187 and cause the _size_ of the buffer to grow, if necessary, to contain all written bytes. 188 On the other hand, `cxBufferTerminate()` writes a zero-byte at the current position, 189 effectively creating a zero-terminated string whose size equals the buffer size. 190 191 The function `cxBufferAppend()` writes the data to the end of the buffer (given by its size) regardless of the current position, 192 and it also does _not_ advance the position. 193 If the write operation triggered a [flush](#flushing), however, the position will be shifted left alongside the shifted buffer contents. 194 In case the data at which the current position points gets flushed, the new position will be zero. 195 196 ## Read 197 198 ```C 199 #include <cx/buffer.h> 200 201 size_t cxBufferRead(void *ptr, size_t size, size_t nitems, 202 CxBuffer *buffer); 203 204 int cxBufferGet(CxBuffer *buffer); 205 206 bool cxBufferEof(const CxBuffer *buffer); 207 ``` 208 209 The function `cxBufferRead()` reads `nitems` number of items of `size` bytes each from the `buffer` 210 and stores them into the memory pointed to by `ptr`, which must be large enough to hold the contents. 211 The function returns the actual bytes read, which might be lower if the desired number of items is not available. 212 213 The function `cxBufferGet()` is a `fgetc()`-like function which returns the next byte in the buffer converted to an `int`. 214 Similar to `fgetc()` it returns `EOF` when there are no more bytes in the buffer. 215 216 When all bytes from the buffer have been read, the function `cxBufferEof()` returns true. 217 218 All read functions advance the position in the buffer by the number of read bytes. 219 220 > When you want to read from a buffer that you previously used for writing, do not forget to set the position 221 > in the buffer to zero, e.g. by calling `cxBufferSeek()`. 222 223 ## Reset and Clear 224 225 ```C 226 #include <cx/buffer.h> 227 228 void cxBufferReset(CxBuffer *buffer); 229 230 void cxBufferClear(CxBuffer *buffer); 231 232 size_t cxBufferPop(CxBuffer *buffer, 233 size_t size, size_t nitems); 234 ``` 235 236 The function `cxBufferReset()` sets both size and position of the buffer to zero, 237 and `cxBufferClear()` additionally uses `memset()` to set every byte in the buffer to zero. 238 239 > When clearing the buffer, only the "live" data, i.e., bytes with indices `[0..size)`, are cleared. 240 > If you want to clear the entire buffer's memory, you would need to set the size to the capacity, first. 241 242 The function `cxBufferPop()` erases the last `nitems` with the given `size` and returns the number of items that could be erased. 243 When the buffer contains fewer bytes than requested and the number of bytes is not divisible by the item `size`, 244 the remainder will stay in the buffer. 245 246 > If the `CX_BUFFER_COPY_ON_WRITE` flag is set, `cxBufferClear()` behaves exactly like `cxBufferReset()`, 247 > because writing to the contents is disallowed. 248 >{style="note"} 249 250 ## Random Access 251 252 ```C 253 #include <cx/buffer.h> 254 255 int cxBufferSeek(CxBuffer *buffer, off_t offset, int whence); 256 ``` 257 258 The function `cxBufferSeek()` is a `fseek()`-like function that sets the current position in the buffer 259 relative to the start (when `whence` is `SEEK_SET`), the current position (when `whence` is `SEEK_CUR`), 260 or end (when `whence` is `SEEK_END`) of the buffer. 261 262 If the resulting position is negative, or larger than the size (i.e. the first unused byte), 263 this function returns non-zero and sets `errno` to `EINVAL`. 264 265 > Note that the behavior of `cxBufferSeek()` when seeking beyond the end of the buffer is not exactly the same as for POSIX `fseek()`. 266 267 ## Shift Contents 268 269 ```C 270 #include <cx/buffer.h> 271 272 int cxBufferShift(CxBuffer *buffer, off_t shift); 273 274 int cxBufferShiftRight(CxBuffer *buffer, size_t shift); 275 276 int cxBufferShiftLeft(CxBuffer *buffer, size_t shift); 277 ``` 278 279 The function `cxBufferShift()` moves the contents within the buffer by the specified `shift` offset, 280 where a negative offset means a shift to the left, and a positive offset means a shift to the right. 281 It also adjusts the current position within the buffer, and in the case of a right shift also the size, by the same offset. 282 283 Data shifted to the left is always discarded when the new position of a byte would be smaller than zero. 284 When bytes are discarded, the new position of the buffer is set to zero. 285 286 When data is shift to the right, the behavior depends on the `CX_BUFFER_AUTO_EXTEND` flag. 287 If set, the function extends the buffer's capacity before moving the data. 288 Otherwise, the function discards all data that would exceed the buffer's capacity, and both the size and the position are equal to the capacity 289 (which means, `cxBufferEof()` returns `true` after the operation). 290 291 The functions `cxBufferShiftRight()` and `cxBufferShiftLeft()` accept a larger (but in both cases positive) shift offset, 292 which usually makes little sense on a 64-bit platform where `off_t` is already large enough to represent any reasonable offset. 293 You may, however, still use those functions to express more explicitly in your code in which direction you want the contents to be shifted. 294 295 ## Flushing 296 297 ```C 298 #include <cx/buffer.h> 299 300 typedef struct cx_buffer_flush_config_s { 301 size_t threshold; 302 size_t blksize; 303 size_t blkmax; 304 void *target; 305 cx_write_func wfunc; 306 } CxBufferFlushConfig; 307 308 int cxBufferEnableFlushing(CxBuffer *buffer, 309 CxBufferFlushConfig config); 310 311 size_t cxBufferFlush(CxBuffer *buffer); 312 ``` 313 314 With the function `cxBufferEnableFlushing()` you can configure a flushing strategy for the contents of the buffer. 315 316 Flushing, once enabled, may happen in the following cases: 317 1. when data is written to the buffer, the capacity is insufficient, and `CX_BUFFER_AUTO_EXTEND` is _not_ enabled 318 2. when data is written to the buffer, and the required capacity exceeds the `threshold` configuration 319 3. when `cxBufferFlush()` is called explicitly 320 321 > By combining the `CX_BUFFER_AUTO_EXTEND` flag and the `threshold` setting, 322 > you can create a buffer that initially starts with a small capacity, grows up to a certain threshold, 323 > and starts flushing only when that threshold is exceeded. 324 325 Flushing happens by invoking the `wfunc` up to `blkmax` times, writing up to `blksize` bytes from the buffer to the `target` with each call. 326 The target might not accept all bytes (i.e., the `wfunc` return value indicates that fewer items have been written than requested), 327 in which case the remaining data remains in the buffer. 328 That means the buffer is effectively [shifted](#shift-contents) left by the number of successfully flushed bytes. 329 330 > When you write large amounts of data to a buffer, multiple flush cycles might happen. 331 > After the first flush operations are completed, the reclaimed space in the buffer is filled first, but if that 332 > is not enough, another flush may be triggered within the same invocation of the write operation. 333 > 334 > That means as much data is written to the buffer and/or flushed as possible, until neither the flush target nor the buffer accept more data. 335 > {style="note"} 336 337 > The function `cxBufferFlush()` simply returns zero when flushing was not enabled via `cxBufferEnableFlushing()`. 338 339 <seealso> 340 <category ref="apidoc"> 341 <a href="https://ucx.sourceforge.io/api/buffer_8h.html">buffer.h</a> 342 </category> 343 </seealso> 344