25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
26 * POSSIBILITY OF SUCH DAMAGE. |
26 * POSSIBILITY OF SUCH DAMAGE. |
27 */ |
27 */ |
28 |
28 |
29 #include "cx/buffer.h" |
29 #include "cx/buffer.h" |
30 #include "cx/utils.h" |
|
31 |
30 |
32 #include <stdio.h> |
31 #include <stdio.h> |
33 #include <string.h> |
32 #include <string.h> |
|
33 #include <errno.h> |
|
34 |
|
35 static int buffer_copy_on_write(CxBuffer* buffer) { |
|
36 if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0; |
|
37 void *newspace = cxMalloc(buffer->allocator, buffer->capacity); |
|
38 if (NULL == newspace) return -1; |
|
39 memcpy(newspace, buffer->space, buffer->size); |
|
40 buffer->space = newspace; |
|
41 buffer->flags &= ~CX_BUFFER_COPY_ON_WRITE; |
|
42 buffer->flags |= CX_BUFFER_FREE_CONTENTS; |
|
43 return 0; |
|
44 } |
34 |
45 |
35 int cxBufferInit( |
46 int cxBufferInit( |
36 CxBuffer *buffer, |
47 CxBuffer *buffer, |
37 void *space, |
48 void *space, |
38 size_t capacity, |
49 size_t capacity, |
39 const CxAllocator *allocator, |
50 const CxAllocator *allocator, |
40 int flags |
51 int flags |
41 ) { |
52 ) { |
42 if (allocator == NULL) allocator = cxDefaultAllocator; |
53 if (allocator == NULL) { |
|
54 allocator = cxDefaultAllocator; |
|
55 } |
|
56 if (flags & CX_BUFFER_COPY_ON_EXTEND) { |
|
57 flags |= CX_BUFFER_AUTO_EXTEND; |
|
58 } |
43 buffer->allocator = allocator; |
59 buffer->allocator = allocator; |
44 buffer->flags = flags; |
60 buffer->flags = flags; |
45 if (!space) { |
61 if (!space) { |
46 buffer->bytes = cxMalloc(allocator, capacity); |
62 buffer->bytes = cxMalloc(allocator, capacity); |
47 if (buffer->bytes == NULL) { |
63 if (buffer->bytes == NULL) { |
48 return 1; |
64 return -1; // LCOV_EXCL_LINE |
49 } |
65 } |
50 buffer->flags |= CX_BUFFER_FREE_CONTENTS; |
66 buffer->flags |= CX_BUFFER_FREE_CONTENTS; |
51 } else { |
67 } else { |
52 buffer->bytes = space; |
68 buffer->bytes = space; |
53 } |
69 } |
54 buffer->capacity = capacity; |
70 buffer->capacity = capacity; |
55 buffer->size = 0; |
71 buffer->size = 0; |
56 buffer->pos = 0; |
72 buffer->pos = 0; |
57 |
73 |
58 buffer->flush_func = NULL; |
74 buffer->flush = NULL; |
59 buffer->flush_target = NULL; |
|
60 buffer->flush_blkmax = 0; |
|
61 buffer->flush_blksize = 4096; |
|
62 buffer->flush_threshold = SIZE_MAX; |
|
63 |
75 |
64 return 0; |
76 return 0; |
65 } |
77 } |
66 |
78 |
|
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)); |
|
86 return 0; |
|
87 } |
|
88 |
67 void cxBufferDestroy(CxBuffer *buffer) { |
89 void cxBufferDestroy(CxBuffer *buffer) { |
68 if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) { |
90 if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { |
69 cxFree(buffer->allocator, buffer->bytes); |
91 cxFree(buffer->allocator, buffer->bytes); |
70 } |
92 } |
|
93 free(buffer->flush); |
|
94 memset(buffer, 0, sizeof(CxBuffer)); |
71 } |
95 } |
72 |
96 |
73 CxBuffer *cxBufferCreate( |
97 CxBuffer *cxBufferCreate( |
74 void *space, |
98 void *space, |
75 size_t capacity, |
99 size_t capacity, |
76 const CxAllocator *allocator, |
100 const CxAllocator *allocator, |
77 int flags |
101 int flags |
78 ) { |
102 ) { |
|
103 if (allocator == NULL) { |
|
104 allocator = cxDefaultAllocator; |
|
105 } |
79 CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer)); |
106 CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer)); |
80 if (buf == NULL) return NULL; |
107 if (buf == NULL) return NULL; |
81 if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) { |
108 if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) { |
82 return buf; |
109 return buf; |
83 } else { |
110 } else { |
|
111 // LCOV_EXCL_START |
84 cxFree(allocator, buf); |
112 cxFree(allocator, buf); |
85 return NULL; |
113 return NULL; |
|
114 // LCOV_EXCL_STOP |
86 } |
115 } |
87 } |
116 } |
88 |
117 |
89 void cxBufferFree(CxBuffer *buffer) { |
118 void cxBufferFree(CxBuffer *buffer) { |
90 if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) { |
119 if (buffer == NULL) return; |
91 cxFree(buffer->allocator, buffer->bytes); |
120 const CxAllocator *allocator = buffer->allocator; |
92 } |
121 cxBufferDestroy(buffer); |
93 cxFree(buffer->allocator, buffer); |
122 cxFree(allocator, buffer); |
94 } |
123 } |
95 |
124 |
96 int cxBufferSeek( |
125 int cxBufferSeek( |
97 CxBuffer *buffer, |
126 CxBuffer *buffer, |
98 off_t offset, |
127 off_t offset, |
150 ) { |
182 ) { |
151 if (newcap <= buffer->capacity) { |
183 if (newcap <= buffer->capacity) { |
152 return 0; |
184 return 0; |
153 } |
185 } |
154 |
186 |
155 if (cxReallocate(buffer->allocator, |
187 const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; |
|
188 if (buffer->flags & force_copy_flags) { |
|
189 void *newspace = cxMalloc(buffer->allocator, newcap); |
|
190 if (NULL == newspace) return -1; |
|
191 memcpy(newspace, buffer->space, buffer->size); |
|
192 buffer->space = newspace; |
|
193 buffer->capacity = newcap; |
|
194 buffer->flags &= ~force_copy_flags; |
|
195 buffer->flags |= CX_BUFFER_FREE_CONTENTS; |
|
196 return 0; |
|
197 } else if (cxReallocate(buffer->allocator, |
156 (void **) &buffer->bytes, newcap) == 0) { |
198 (void **) &buffer->bytes, newcap) == 0) { |
157 buffer->capacity = newcap; |
199 buffer->capacity = newcap; |
158 return 0; |
200 return 0; |
159 } else { |
201 } else { |
160 return -1; |
202 return -1; // LCOV_EXCL_LINE |
161 } |
203 } |
162 } |
204 } |
163 |
205 |
164 /** |
206 static size_t cx_buffer_flush_helper( |
165 * Helps flushing data to the flush target of a buffer. |
207 const CxBuffer *buffer, |
166 * |
|
167 * @param buffer the buffer containing the config |
|
168 * @param space the data to flush |
|
169 * @param size the element size |
|
170 * @param nitems the number of items |
|
171 * @return the number of items flushed |
|
172 */ |
|
173 static size_t cx_buffer_write_flush_helper( |
|
174 CxBuffer *buffer, |
|
175 const unsigned char *space, |
|
176 size_t size, |
208 size_t size, |
|
209 const unsigned char *src, |
177 size_t nitems |
210 size_t nitems |
178 ) { |
211 ) { |
179 size_t pos = 0; |
212 // flush data from an arbitrary source |
180 size_t remaining = nitems; |
213 // does not need to be the buffer's contents |
181 size_t max_items = buffer->flush_blksize / size; |
214 size_t max_items = buffer->flush->blksize / size; |
182 while (remaining > 0) { |
215 size_t fblocks = 0; |
183 size_t items = remaining > max_items ? max_items : remaining; |
216 size_t flushed_total = 0; |
184 size_t flushed = buffer->flush_func( |
217 while (nitems > 0 && fblocks < buffer->flush->blkmax) { |
185 space + pos, |
218 fblocks++; |
186 size, items, |
219 size_t items = nitems > max_items ? max_items : nitems; |
187 buffer->flush_target); |
220 size_t flushed = buffer->flush->wfunc( |
|
221 src, size, items, buffer->flush->target); |
188 if (flushed > 0) { |
222 if (flushed > 0) { |
189 pos += (flushed * size); |
223 flushed_total += flushed; |
190 remaining -= flushed; |
224 src += flushed * size; |
|
225 nitems -= flushed; |
191 } else { |
226 } else { |
192 // if no bytes can be flushed out anymore, we give up |
227 // if no bytes can be flushed out anymore, we give up |
193 break; |
228 break; |
194 } |
229 } |
195 } |
230 } |
196 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); |
197 } |
252 } |
198 |
253 |
199 size_t cxBufferWrite( |
254 size_t cxBufferWrite( |
200 const void *ptr, |
255 const void *ptr, |
201 size_t size, |
256 size_t size, |
202 size_t nitems, |
257 size_t nitems, |
203 CxBuffer *buffer |
258 CxBuffer *buffer |
204 ) { |
259 ) { |
205 // optimize for easy case |
260 // optimize for easy case |
206 if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) { |
261 if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) { |
|
262 if (buffer_copy_on_write(buffer)) return 0; |
207 memcpy(buffer->bytes + buffer->pos, ptr, nitems); |
263 memcpy(buffer->bytes + buffer->pos, ptr, nitems); |
208 buffer->pos += nitems; |
264 buffer->pos += nitems; |
209 if (buffer->pos > buffer->size) { |
265 if (buffer->pos > buffer->size) { |
210 buffer->size = buffer->pos; |
266 buffer->size = buffer->pos; |
211 } |
267 } |
212 return nitems; |
268 return nitems; |
213 } |
269 } |
214 |
270 |
215 size_t len; |
271 size_t len; |
216 size_t nitems_out = nitems; |
|
217 if (cx_szmul(size, nitems, &len)) { |
272 if (cx_szmul(size, nitems, &len)) { |
|
273 errno = EOVERFLOW; |
|
274 return 0; |
|
275 } |
|
276 if (buffer->pos > SIZE_MAX - len) { |
|
277 errno = EOVERFLOW; |
218 return 0; |
278 return 0; |
219 } |
279 } |
220 size_t required = buffer->pos + len; |
280 size_t required = buffer->pos + len; |
221 if (buffer->pos > required) { |
|
222 return 0; |
|
223 } |
|
224 |
281 |
225 bool perform_flush = false; |
282 bool perform_flush = false; |
226 if (required > buffer->capacity) { |
283 if (required > buffer->capacity) { |
227 if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND && required) { |
284 if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { |
228 if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) { |
285 if (buffer->flush != NULL && required > buffer->flush->threshold) { |
229 perform_flush = true; |
286 perform_flush = true; |
230 } else { |
287 } else { |
231 if (cxBufferMinimumCapacity(buffer, required)) { |
288 if (cxBufferMinimumCapacity(buffer, required)) { |
232 return 0; |
289 return 0; // LCOV_EXCL_LINE |
233 } |
290 } |
234 } |
291 } |
235 } else { |
292 } else { |
236 if (buffer->flush_blkmax > 0) { |
293 if (buffer->flush != NULL) { |
237 perform_flush = true; |
294 perform_flush = true; |
238 } else { |
295 } else { |
239 // truncate data to be written, if we can neither extend nor flush |
296 // truncate data, if we can neither extend nor flush |
240 len = buffer->capacity - buffer->pos; |
297 len = buffer->capacity - buffer->pos; |
241 if (size > 1) { |
298 if (size > 1) { |
242 len -= len % size; |
299 len -= len % size; |
243 } |
300 } |
244 nitems_out = len / size; |
301 nitems = len / size; |
245 } |
302 } |
246 } |
303 } |
247 } |
304 } |
248 |
305 |
|
306 // check here and not above because of possible truncation |
249 if (len == 0) { |
307 if (len == 0) { |
250 return len; |
308 return 0; |
251 } |
309 } |
252 |
310 |
|
311 // check if we need to copy |
|
312 if (buffer_copy_on_write(buffer)) return 0; |
|
313 |
|
314 // perform the operation |
253 if (perform_flush) { |
315 if (perform_flush) { |
254 size_t flush_max; |
316 size_t items_flush; |
255 if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) { |
317 if (buffer->pos == 0) { |
256 return 0; |
318 // if we don't have data in the buffer, but are instructed |
257 } |
319 // to flush, it means that we are supposed to relay the data |
258 size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL |
320 items_flush = cx_buffer_flush_helper(buffer, size, ptr, nitems); |
259 ? buffer->pos |
321 if (items_flush == 0) { |
260 : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos); |
322 // we needed to flush, but could not flush anything |
261 if (flush_pos == buffer->pos) { |
323 // give up and avoid endless trying |
262 // entire buffer has been flushed, we can reset |
324 return 0; |
263 buffer->size = buffer->pos = 0; |
|
264 |
|
265 size_t items_flush; // how many items can also be directly flushed |
|
266 size_t items_keep; // how many items have to be written to the buffer |
|
267 |
|
268 items_flush = flush_max >= required ? nitems : (flush_max - flush_pos) / size; |
|
269 if (items_flush > 0) { |
|
270 items_flush = cx_buffer_write_flush_helper(buffer, ptr, size, items_flush / size); |
|
271 // in case we could not flush everything, keep the rest |
|
272 } |
325 } |
273 items_keep = nitems - items_flush; |
326 size_t ritems = nitems - items_flush; |
274 if (items_keep > 0) { |
327 const unsigned char *rest = ptr; |
275 // try again with the remaining stuff |
328 rest += items_flush * size; |
276 const unsigned char *new_ptr = ptr; |
329 return items_flush + cxBufferWrite(rest, size, ritems, buffer); |
277 new_ptr += items_flush * size; |
330 } else { |
278 // report the directly flushed items as written plus the remaining stuff |
331 items_flush = cx_buffer_flush_impl(buffer, size); |
279 return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer); |
332 if (items_flush == 0) { |
280 } else { |
333 return 0; |
281 // all items have been flushed - report them as written |
|
282 return nitems; |
|
283 } |
334 } |
284 } else if (flush_pos == 0) { |
|
285 // nothing could be flushed at all, we immediately give up without writing any data |
|
286 return 0; |
|
287 } else { |
|
288 // we were partially successful, we shift left and try again |
|
289 cxBufferShiftLeft(buffer, flush_pos); |
|
290 return cxBufferWrite(ptr, size, nitems, buffer); |
335 return cxBufferWrite(ptr, size, nitems, buffer); |
291 } |
336 } |
292 } else { |
337 } else { |
293 memcpy(buffer->bytes + buffer->pos, ptr, len); |
338 memcpy(buffer->bytes + buffer->pos, ptr, len); |
294 buffer->pos += len; |
339 buffer->pos += len; |
295 if (buffer->pos > buffer->size) { |
340 if (buffer->pos > buffer->size) { |
296 buffer->size = buffer->pos; |
341 buffer->size = buffer->pos; |
297 } |
342 } |
298 return nitems_out; |
343 return nitems; |
299 } |
344 } |
300 |
345 |
|
346 } |
|
347 |
|
348 size_t cxBufferAppend( |
|
349 const void *ptr, |
|
350 size_t size, |
|
351 size_t nitems, |
|
352 CxBuffer *buffer |
|
353 ) { |
|
354 size_t pos = buffer->pos; |
|
355 buffer->pos = buffer->size; |
|
356 size_t written = cxBufferWrite(ptr, size, nitems, buffer); |
|
357 buffer->pos = pos; |
|
358 return written; |
301 } |
359 } |
302 |
360 |
303 int cxBufferPut( |
361 int cxBufferPut( |
304 CxBuffer *buffer, |
362 CxBuffer *buffer, |
305 int c |
363 int c |