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( |