| 64 buffer->flags |= CX_BUFFER_FREE_CONTENTS; |
64 buffer->flags |= CX_BUFFER_FREE_CONTENTS; |
| 65 } else { |
65 } else { |
| 66 buffer->bytes = space; |
66 buffer->bytes = space; |
| 67 } |
67 } |
| 68 buffer->capacity = capacity; |
68 buffer->capacity = capacity; |
| |
69 buffer->max_capacity = SIZE_MAX; |
| 69 buffer->size = 0; |
70 buffer->size = 0; |
| 70 buffer->pos = 0; |
71 buffer->pos = 0; |
| 71 |
72 |
| 72 buffer->flush = NULL; |
|
| 73 |
|
| 74 return 0; |
|
| 75 } |
|
| 76 |
|
| 77 int cxBufferEnableFlushing( |
|
| 78 CxBuffer *buffer, |
|
| 79 CxBufferFlushConfig config |
|
| 80 ) { |
|
| 81 buffer->flush = cxMallocDefault(sizeof(CxBufferFlushConfig)); |
|
| 82 if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE |
|
| 83 memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig)); |
|
| 84 return 0; |
73 return 0; |
| 85 } |
74 } |
| 86 |
75 |
| 87 void cxBufferDestroy(CxBuffer *buffer) { |
76 void cxBufferDestroy(CxBuffer *buffer) { |
| 88 if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { |
77 if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { |
| 89 cxFree(buffer->allocator, buffer->bytes); |
78 cxFree(buffer->allocator, buffer->bytes); |
| 90 } |
79 } |
| 91 cxFreeDefault(buffer->flush); |
|
| 92 memset(buffer, 0, sizeof(CxBuffer)); |
80 memset(buffer, 0, sizeof(CxBuffer)); |
| 93 } |
81 } |
| 94 |
82 |
| 95 CxBuffer *cxBufferCreate( |
83 CxBuffer *cxBufferCreate( |
| 96 void *space, |
84 void *space, |
| 211 |
199 |
| 212 int cxBufferReserve(CxBuffer *buffer, size_t newcap) { |
200 int cxBufferReserve(CxBuffer *buffer, size_t newcap) { |
| 213 if (newcap == buffer->capacity) { |
201 if (newcap == buffer->capacity) { |
| 214 return 0; |
202 return 0; |
| 215 } |
203 } |
| |
204 if (newcap > buffer->max_capacity) { |
| |
205 return -1; |
| |
206 } |
| 216 const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; |
207 const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; |
| 217 if (buffer->flags & force_copy_flags) { |
208 if (buffer->flags & force_copy_flags) { |
| 218 void *newspace = cxMalloc(buffer->allocator, newcap); |
209 void *newspace = cxMalloc(buffer->allocator, newcap); |
| 219 if (NULL == newspace) return -1; |
210 if (NULL == newspace) return -1; |
| 220 memcpy(newspace, buffer->space, buffer->size); |
211 memcpy(newspace, buffer->space, buffer->size); |
| 234 } else { |
225 } else { |
| 235 return -1; // LCOV_EXCL_LINE |
226 return -1; // LCOV_EXCL_LINE |
| 236 } |
227 } |
| 237 } |
228 } |
| 238 |
229 |
| 239 static size_t cx_buffer_calculate_minimum_capacity(size_t mincap) { |
230 int cxBufferMaximumCapacity(CxBuffer *buffer, size_t capacity) { |
| 240 unsigned long pagesize = cx_system_page_size(); |
231 if (capacity < buffer->capacity) { |
| 241 // if page size is larger than 64 KB - for some reason - truncate to 64 KB |
232 return -1; |
| 242 if (pagesize > 65536) pagesize = 65536; |
233 } |
| 243 if (mincap < pagesize) { |
234 buffer->max_capacity = capacity; |
| 244 // when smaller as one page, map to the next power of two |
235 return 0; |
| 245 mincap--; |
|
| 246 mincap |= mincap >> 1; |
|
| 247 mincap |= mincap >> 2; |
|
| 248 mincap |= mincap >> 4; |
|
| 249 // last operation only needed for pages larger 4096 bytes |
|
| 250 // but if/else would be more expensive than just doing this |
|
| 251 mincap |= mincap >> 8; |
|
| 252 mincap++; |
|
| 253 } else { |
|
| 254 // otherwise, map to a multiple of the page size |
|
| 255 mincap -= mincap % pagesize; |
|
| 256 mincap += pagesize; |
|
| 257 // note: if newcap is already page aligned, |
|
| 258 // this gives a full additional page (which is good) |
|
| 259 } |
|
| 260 return mincap; |
|
| 261 } |
236 } |
| 262 |
237 |
| 263 int cxBufferMinimumCapacity(CxBuffer *buffer, size_t newcap) { |
238 int cxBufferMinimumCapacity(CxBuffer *buffer, size_t newcap) { |
| 264 if (newcap <= buffer->capacity) { |
239 if (newcap <= buffer->capacity) { |
| 265 return 0; |
240 return 0; |
| 266 } |
241 } |
| 267 newcap = cx_buffer_calculate_minimum_capacity(newcap); |
242 if (newcap > buffer->max_capacity) { |
| |
243 return -1; |
| |
244 } |
| |
245 if (newcap < buffer->max_capacity) { |
| |
246 unsigned long pagesize = cx_system_page_size(); |
| |
247 // if page size is larger than 64 KB - for some reason - truncate to 64 KB |
| |
248 if (pagesize > 65536) pagesize = 65536; |
| |
249 if (newcap < pagesize) { |
| |
250 // when smaller as one page, map to the next power of two |
| |
251 newcap--; |
| |
252 newcap |= newcap >> 1; |
| |
253 newcap |= newcap >> 2; |
| |
254 newcap |= newcap >> 4; |
| |
255 // last operation only needed for pages larger 4096 bytes |
| |
256 // but if/else would be more expensive than just doing this |
| |
257 newcap |= newcap >> 8; |
| |
258 newcap++; |
| |
259 } else { |
| |
260 // otherwise, map to a multiple of the page size |
| |
261 newcap -= newcap % pagesize; |
| |
262 newcap += pagesize; |
| |
263 // note: if newcap is already page aligned, |
| |
264 // this gives a full additional page (which is good) |
| |
265 } |
| |
266 if (newcap > buffer->max_capacity) { |
| |
267 newcap = buffer->max_capacity; |
| |
268 } |
| |
269 } |
| 268 return cxBufferReserve(buffer, newcap); |
270 return cxBufferReserve(buffer, newcap); |
| 269 } |
271 } |
| 270 |
272 |
| 271 void cxBufferShrink( |
273 void cxBufferShrink( |
| 272 CxBuffer *buffer, |
274 CxBuffer *buffer, |
| 286 if (newCapacity < buffer->capacity) { |
288 if (newCapacity < buffer->capacity) { |
| 287 if (0 == cxReallocate(buffer->allocator, &buffer->bytes, newCapacity)) { |
289 if (0 == cxReallocate(buffer->allocator, &buffer->bytes, newCapacity)) { |
| 288 buffer->capacity = newCapacity; |
290 buffer->capacity = newCapacity; |
| 289 } |
291 } |
| 290 } |
292 } |
| 291 } |
|
| 292 |
|
| 293 static size_t cx_buffer_flush_helper( |
|
| 294 const CxBuffer *buffer, |
|
| 295 const unsigned char *src, |
|
| 296 size_t size, |
|
| 297 size_t nitems |
|
| 298 ) { |
|
| 299 // flush data from an arbitrary source |
|
| 300 // does not need to be the buffer's contents |
|
| 301 size_t max_items = buffer->flush->blksize / size; |
|
| 302 size_t fblocks = 0; |
|
| 303 size_t flushed_total = 0; |
|
| 304 while (nitems > 0 && fblocks < buffer->flush->blkmax) { |
|
| 305 fblocks++; |
|
| 306 size_t items = nitems > max_items ? max_items : nitems; |
|
| 307 size_t flushed = buffer->flush->wfunc( |
|
| 308 src, size, items, buffer->flush->target); |
|
| 309 if (flushed > 0) { |
|
| 310 flushed_total += flushed; |
|
| 311 src += flushed * size; |
|
| 312 nitems -= flushed; |
|
| 313 } else { |
|
| 314 // if no bytes can be flushed out anymore, we give up |
|
| 315 break; |
|
| 316 } |
|
| 317 } |
|
| 318 return flushed_total; |
|
| 319 } |
|
| 320 |
|
| 321 static size_t cx_buffer_flush_impl(CxBuffer *buffer, size_t size) { |
|
| 322 // flush the current contents of the buffer |
|
| 323 unsigned char *space = buffer->bytes; |
|
| 324 size_t remaining = buffer->pos / size; |
|
| 325 size_t flushed_total = cx_buffer_flush_helper( |
|
| 326 buffer, space, size, remaining); |
|
| 327 |
|
| 328 // shift the buffer left after flushing |
|
| 329 // IMPORTANT: up to this point, copy on write must have been |
|
| 330 // performed already, because we can't do error handling here |
|
| 331 cxBufferShiftLeft(buffer, flushed_total*size); |
|
| 332 |
|
| 333 return flushed_total; |
|
| 334 } |
|
| 335 |
|
| 336 size_t cxBufferFlush(CxBuffer *buffer) { |
|
| 337 if (buffer_copy_on_write(buffer)) return 0; |
|
| 338 return cx_buffer_flush_impl(buffer, 1); |
|
| 339 } |
293 } |
| 340 |
294 |
| 341 size_t cxBufferWrite( |
295 size_t cxBufferWrite( |
| 342 const void *ptr, |
296 const void *ptr, |
| 343 size_t size, |
297 size_t size, |
| 353 buffer->size = buffer->pos; |
307 buffer->size = buffer->pos; |
| 354 } |
308 } |
| 355 return nitems; |
309 return nitems; |
| 356 } |
310 } |
| 357 |
311 |
| 358 size_t len, total_flushed = 0; |
312 size_t len; |
| 359 cx_buffer_write_retry: |
|
| 360 if (cx_szmul(size, nitems, &len)) { |
313 if (cx_szmul(size, nitems, &len)) { |
| 361 errno = EOVERFLOW; |
314 errno = EOVERFLOW; |
| 362 return total_flushed; |
315 return 0; |
| 363 } |
316 } |
| 364 if (buffer->pos > SIZE_MAX - len) { |
317 if (buffer->pos > SIZE_MAX - len) { |
| 365 errno = EOVERFLOW; |
318 errno = EOVERFLOW; |
| 366 return total_flushed; |
319 return 0; |
| 367 } |
320 } |
| 368 |
321 const size_t required = buffer->pos + len; |
| 369 size_t required = buffer->pos + len; |
322 |
| 370 bool perform_flush = false; |
323 // check if we need to auto-extend |
| 371 if (required > buffer->capacity) { |
324 if (required > buffer->capacity) { |
| 372 if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { |
325 if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { |
| 373 if (buffer->flush != NULL) { |
326 size_t newcap = required < buffer->max_capacity |
| 374 size_t newcap = cx_buffer_calculate_minimum_capacity(required); |
327 ? required : buffer->max_capacity; |
| 375 if (newcap > buffer->flush->threshold) { |
328 if (cxBufferMinimumCapacity(buffer, newcap)) { |
| 376 newcap = buffer->flush->threshold; |
329 return 0; // LCOV_EXCL_LINE |
| 377 } |
|
| 378 if (cxBufferReserve(buffer, newcap)) { |
|
| 379 return total_flushed; // LCOV_EXCL_LINE |
|
| 380 } |
|
| 381 if (required > newcap) { |
|
| 382 perform_flush = true; |
|
| 383 } |
|
| 384 } else { |
|
| 385 if (cxBufferMinimumCapacity(buffer, required)) { |
|
| 386 return total_flushed; // LCOV_EXCL_LINE |
|
| 387 } |
|
| 388 } |
330 } |
| 389 } else { |
331 } |
| 390 if (buffer->flush != NULL) { |
332 } |
| 391 perform_flush = true; |
333 |
| 392 } else { |
334 // check again and truncate data if capacity is still not enough |
| 393 // truncate data, if we can neither extend nor flush |
335 if (required > buffer->capacity) { |
| 394 len = buffer->capacity - buffer->pos; |
336 len = buffer->capacity - buffer->pos; |
| 395 if (size > 1) { |
337 if (size > 1) { |
| 396 len -= len % size; |
338 len -= len % size; |
| 397 } |
339 } |
| 398 nitems = len / size; |
340 nitems = len / size; |
| 399 } |
|
| 400 } |
|
| 401 } |
341 } |
| 402 |
342 |
| 403 // check here and not above because of possible truncation |
343 // check here and not above because of possible truncation |
| 404 if (len == 0) { |
344 if (len == 0) { |
| 405 return total_flushed; |
345 return 0; |
| 406 } |
346 } |
| 407 |
347 |
| 408 // check if we need to copy |
348 // check if we need to copy |
| 409 if (buffer_copy_on_write(buffer)) return 0; |
349 if (buffer_copy_on_write(buffer)) return 0; |
| 410 |
350 |
| 411 // perform the operation |
351 // perform the operation |
| 412 if (perform_flush) { |
352 memcpy(buffer->bytes + buffer->pos, ptr, len); |
| 413 size_t items_flushed; |
353 buffer->pos += len; |
| 414 if (buffer->pos == 0) { |
354 if (buffer->pos > buffer->size) { |
| 415 // if we don't have data in the buffer, but are instructed |
355 buffer->size = buffer->pos; |
| 416 // to flush, it means that we are supposed to relay the data |
356 } |
| 417 items_flushed = cx_buffer_flush_helper(buffer, ptr, size, nitems); |
357 return nitems; |
| 418 if (items_flushed == 0) { |
|
| 419 // we needed to relay data, but could not flush anything |
|
| 420 // i.e. we have to give up to avoid endless trying |
|
| 421 return 0; |
|
| 422 } |
|
| 423 nitems -= items_flushed; |
|
| 424 total_flushed += items_flushed; |
|
| 425 if (nitems > 0) { |
|
| 426 ptr = ((unsigned char*)ptr) + items_flushed * size; |
|
| 427 goto cx_buffer_write_retry; |
|
| 428 } |
|
| 429 return total_flushed; |
|
| 430 } else { |
|
| 431 items_flushed = cx_buffer_flush_impl(buffer, size); |
|
| 432 if (items_flushed == 0) { |
|
| 433 // flush target is full, let's try to truncate |
|
| 434 size_t remaining_space; |
|
| 435 if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { |
|
| 436 remaining_space = buffer->flush->threshold > buffer->pos |
|
| 437 ? buffer->flush->threshold - buffer->pos |
|
| 438 : 0; |
|
| 439 } else { |
|
| 440 remaining_space = buffer->capacity > buffer->pos |
|
| 441 ? buffer->capacity - buffer->pos |
|
| 442 : 0; |
|
| 443 } |
|
| 444 nitems = remaining_space / size; |
|
| 445 if (nitems == 0) { |
|
| 446 return total_flushed; |
|
| 447 } |
|
| 448 } |
|
| 449 goto cx_buffer_write_retry; |
|
| 450 } |
|
| 451 } else { |
|
| 452 memcpy(buffer->bytes + buffer->pos, ptr, len); |
|
| 453 buffer->pos += len; |
|
| 454 if (buffer->pos > buffer->size) { |
|
| 455 buffer->size = buffer->pos; |
|
| 456 } |
|
| 457 return total_flushed + nitems; |
|
| 458 } |
|
| 459 } |
358 } |
| 460 |
359 |
| 461 size_t cxBufferAppend( |
360 size_t cxBufferAppend( |
| 462 const void *ptr, |
361 const void *ptr, |
| 463 size_t size, |
362 size_t size, |