| 69 } |
69 } |
| 70 buffer->capacity = capacity; |
70 buffer->capacity = capacity; |
| 71 buffer->size = 0; |
71 buffer->size = 0; |
| 72 buffer->pos = 0; |
72 buffer->pos = 0; |
| 73 |
73 |
| 74 buffer->flush_func = NULL; |
74 buffer->flush = NULL; |
| 75 buffer->flush_target = NULL; |
75 |
| 76 buffer->flush_blkmax = 0; |
76 return 0; |
| 77 buffer->flush_blksize = 4096; |
77 } |
| 78 buffer->flush_threshold = SIZE_MAX; |
78 |
| 79 |
79 int cxBufferEnableFlushing( |
| |
80 CxBuffer *buffer, |
| |
81 CxBufferFlushConfig config |
| |
82 ) { |
| |
83 buffer->flush = malloc(sizeof(CxBufferFlushConfig)); |
| |
84 if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE |
| |
85 memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig)); |
| 80 return 0; |
86 return 0; |
| 81 } |
87 } |
| 82 |
88 |
| 83 void cxBufferDestroy(CxBuffer *buffer) { |
89 void cxBufferDestroy(CxBuffer *buffer) { |
| 84 if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { |
90 if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { |
| 85 cxFree(buffer->allocator, buffer->bytes); |
91 cxFree(buffer->allocator, buffer->bytes); |
| 86 } |
92 } |
| |
93 free(buffer->flush); |
| 87 memset(buffer, 0, sizeof(CxBuffer)); |
94 memset(buffer, 0, sizeof(CxBuffer)); |
| 88 } |
95 } |
| 89 |
96 |
| 90 CxBuffer *cxBufferCreate( |
97 CxBuffer *cxBufferCreate( |
| 91 void *space, |
98 void *space, |
| 194 } else { |
201 } else { |
| 195 return -1; // LCOV_EXCL_LINE |
202 return -1; // LCOV_EXCL_LINE |
| 196 } |
203 } |
| 197 } |
204 } |
| 198 |
205 |
| 199 /** |
206 static size_t cx_buffer_flush_helper( |
| 200 * Helps flushing data to the flush target of a buffer. |
207 const CxBuffer *buffer, |
| 201 * |
|
| 202 * @param buffer the buffer containing the config |
|
| 203 * @param space the data to flush |
|
| 204 * @param size the element size |
|
| 205 * @param nitems the number of items |
|
| 206 * @return the number of items flushed |
|
| 207 */ |
|
| 208 static size_t cx_buffer_write_flush_helper( |
|
| 209 CxBuffer *buffer, |
|
| 210 const unsigned char *space, |
|
| 211 size_t size, |
208 size_t size, |
| |
209 const unsigned char *src, |
| 212 size_t nitems |
210 size_t nitems |
| 213 ) { |
211 ) { |
| 214 size_t pos = 0; |
212 // flush data from an arbitrary source |
| 215 size_t remaining = nitems; |
213 // does not need to be the buffer's contents |
| 216 size_t max_items = buffer->flush_blksize / size; |
214 size_t max_items = buffer->flush->blksize / size; |
| 217 while (remaining > 0) { |
215 size_t fblocks = 0; |
| 218 size_t items = remaining > max_items ? max_items : remaining; |
216 size_t flushed_total = 0; |
| 219 size_t flushed = buffer->flush_func( |
217 while (nitems > 0 && fblocks < buffer->flush->blkmax) { |
| 220 space + pos, |
218 fblocks++; |
| 221 size, items, |
219 size_t items = nitems > max_items ? max_items : nitems; |
| 222 buffer->flush_target); |
220 size_t flushed = buffer->flush->wfunc( |
| |
221 src, size, items, buffer->flush->target); |
| 223 if (flushed > 0) { |
222 if (flushed > 0) { |
| 224 pos += (flushed * size); |
223 flushed_total += flushed; |
| 225 remaining -= flushed; |
224 src += flushed * size; |
| |
225 nitems -= flushed; |
| 226 } else { |
226 } else { |
| 227 // if no bytes can be flushed out anymore, we give up |
227 // if no bytes can be flushed out anymore, we give up |
| 228 break; |
228 break; |
| 229 } |
229 } |
| 230 } |
230 } |
| 231 return nitems - remaining; |
231 return flushed_total; |
| |
232 } |
| |
233 |
| |
234 static size_t cx_buffer_flush_impl(CxBuffer *buffer, size_t size) { |
| |
235 // flush the current contents of the buffer |
| |
236 unsigned char *space = buffer->bytes; |
| |
237 size_t remaining = buffer->pos / size; |
| |
238 size_t flushed_total = cx_buffer_flush_helper( |
| |
239 buffer, size, space, remaining); |
| |
240 |
| |
241 // shift the buffer left after flushing |
| |
242 // IMPORTANT: up to this point, copy on write must have been |
| |
243 // performed already, because we can't do error handling here |
| |
244 cxBufferShiftLeft(buffer, flushed_total*size); |
| |
245 |
| |
246 return flushed_total; |
| |
247 } |
| |
248 |
| |
249 size_t cxBufferFlush(CxBuffer *buffer) { |
| |
250 if (buffer_copy_on_write(buffer)) return 0; |
| |
251 return cx_buffer_flush_impl(buffer, 1); |
| 232 } |
252 } |
| 233 |
253 |
| 234 size_t cxBufferWrite( |
254 size_t cxBufferWrite( |
| 235 const void *ptr, |
255 const void *ptr, |
| 236 size_t size, |
256 size_t size, |
| 247 } |
267 } |
| 248 return nitems; |
268 return nitems; |
| 249 } |
269 } |
| 250 |
270 |
| 251 size_t len; |
271 size_t len; |
| 252 size_t nitems_out = nitems; |
|
| 253 if (cx_szmul(size, nitems, &len)) { |
272 if (cx_szmul(size, nitems, &len)) { |
| 254 errno = EOVERFLOW; |
273 errno = EOVERFLOW; |
| 255 return 0; |
274 return 0; |
| 256 } |
275 } |
| |
276 if (buffer->pos > SIZE_MAX - len) { |
| |
277 errno = EOVERFLOW; |
| |
278 return 0; |
| |
279 } |
| 257 size_t required = buffer->pos + len; |
280 size_t required = buffer->pos + len; |
| 258 if (buffer->pos > required) { |
|
| 259 return 0; |
|
| 260 } |
|
| 261 |
281 |
| 262 bool perform_flush = false; |
282 bool perform_flush = false; |
| 263 if (required > buffer->capacity) { |
283 if (required > buffer->capacity) { |
| 264 if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { |
284 if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { |
| 265 if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) { |
285 if (buffer->flush != NULL && required > buffer->flush->threshold) { |
| 266 perform_flush = true; |
286 perform_flush = true; |
| 267 } else { |
287 } else { |
| 268 if (cxBufferMinimumCapacity(buffer, required)) { |
288 if (cxBufferMinimumCapacity(buffer, required)) { |
| 269 return 0; // LCOV_EXCL_LINE |
289 return 0; // LCOV_EXCL_LINE |
| 270 } |
290 } |
| 271 } |
291 } |
| 272 } else { |
292 } else { |
| 273 if (buffer->flush_blkmax > 0) { |
293 if (buffer->flush != NULL) { |
| 274 perform_flush = true; |
294 perform_flush = true; |
| 275 } else { |
295 } else { |
| 276 // truncate data to be written, if we can neither extend nor flush |
296 // truncate data, if we can neither extend nor flush |
| 277 len = buffer->capacity - buffer->pos; |
297 len = buffer->capacity - buffer->pos; |
| 278 if (size > 1) { |
298 if (size > 1) { |
| 279 len -= len % size; |
299 len -= len % size; |
| 280 } |
300 } |
| 281 nitems_out = len / size; |
301 nitems = len / size; |
| 282 } |
302 } |
| 283 } |
303 } |
| 284 } |
304 } |
| 285 |
305 |
| |
306 // check here and not above because of possible truncation |
| 286 if (len == 0) { |
307 if (len == 0) { |
| 287 return len; |
308 return 0; |
| 288 } |
309 } |
| 289 |
310 |
| |
311 // check if we need to copy |
| |
312 if (buffer_copy_on_write(buffer)) return 0; |
| |
313 |
| |
314 // perform the operation |
| 290 if (perform_flush) { |
315 if (perform_flush) { |
| 291 size_t flush_max; |
316 size_t items_flush; |
| 292 if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) { |
317 if (buffer->pos == 0) { |
| 293 errno = EOVERFLOW; |
318 // if we don't have data in the buffer, but are instructed |
| 294 return 0; |
319 // to flush, it means that we are supposed to relay the data |
| 295 } |
320 items_flush = cx_buffer_flush_helper(buffer, size, ptr, nitems); |
| 296 size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL |
321 if (items_flush == 0) { |
| 297 ? buffer->pos |
322 // we needed to flush, but could not flush anything |
| 298 : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos); |
323 // give up and avoid endless trying |
| 299 if (flush_pos == buffer->pos) { |
324 return 0; |
| 300 // entire buffer has been flushed, we can reset |
|
| 301 buffer->size = buffer->pos = 0; |
|
| 302 |
|
| 303 size_t items_flush; // how many items can also be directly flushed |
|
| 304 size_t items_keep; // how many items have to be written to the buffer |
|
| 305 |
|
| 306 items_flush = flush_max >= required ? nitems : (flush_max - flush_pos) / size; |
|
| 307 if (items_flush > 0) { |
|
| 308 items_flush = cx_buffer_write_flush_helper(buffer, ptr, size, items_flush / size); |
|
| 309 // in case we could not flush everything, keep the rest |
|
| 310 } |
325 } |
| 311 items_keep = nitems - items_flush; |
326 size_t ritems = nitems - items_flush; |
| 312 if (items_keep > 0) { |
327 const unsigned char *rest = ptr; |
| 313 // try again with the remaining stuff |
328 rest += items_flush * size; |
| 314 const unsigned char *new_ptr = ptr; |
329 return items_flush + cxBufferWrite(rest, size, ritems, buffer); |
| 315 new_ptr += items_flush * size; |
330 } else { |
| 316 // report the directly flushed items as written plus the remaining stuff |
331 items_flush = cx_buffer_flush_impl(buffer, size); |
| 317 return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer); |
332 if (items_flush == 0) { |
| 318 } else { |
333 return 0; |
| 319 // all items have been flushed - report them as written |
|
| 320 return nitems; |
|
| 321 } |
334 } |
| 322 } else if (flush_pos == 0) { |
|
| 323 // nothing could be flushed at all, we immediately give up without writing any data |
|
| 324 return 0; |
|
| 325 } else { |
|
| 326 // we were partially successful, we shift left and try again |
|
| 327 cxBufferShiftLeft(buffer, flush_pos); |
|
| 328 return cxBufferWrite(ptr, size, nitems, buffer); |
335 return cxBufferWrite(ptr, size, nitems, buffer); |
| 329 } |
336 } |
| 330 } else { |
337 } else { |
| 331 if (buffer_copy_on_write(buffer)) return 0; |
|
| 332 memcpy(buffer->bytes + buffer->pos, ptr, len); |
338 memcpy(buffer->bytes + buffer->pos, ptr, len); |
| 333 buffer->pos += len; |
339 buffer->pos += len; |
| 334 if (buffer->pos > buffer->size) { |
340 if (buffer->pos > buffer->size) { |
| 335 buffer->size = buffer->pos; |
341 buffer->size = buffer->pos; |
| 336 } |
342 } |
| 337 return nitems_out; |
343 return nitems; |
| 338 } |
344 } |
| 339 |
345 |
| 340 } |
346 } |
| 341 |
347 |
| 342 size_t cxBufferAppend( |
348 size_t cxBufferAppend( |