Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/external/curl/lib/bufq.c
2647 views
1
/***************************************************************************
2
* _ _ ____ _
3
* Project ___| | | | _ \| |
4
* / __| | | | |_) | |
5
* | (__| |_| | _ <| |___
6
* \___|\___/|_| \_\_____|
7
*
8
* Copyright (C) Daniel Stenberg, <[email protected]>, et al.
9
*
10
* This software is licensed as described in the file COPYING, which
11
* you should have received as part of this distribution. The terms
12
* are also available at https://curl.se/docs/copyright.html.
13
*
14
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
* copies of the Software, and permit persons to whom the Software is
16
* furnished to do so, under the terms of the COPYING file.
17
*
18
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
* KIND, either express or implied.
20
*
21
* SPDX-License-Identifier: curl
22
*
23
***************************************************************************/
24
25
#include "curl_setup.h"
26
#include "bufq.h"
27
28
/* The last 2 #include files should be in this order */
29
#include "curl_memory.h"
30
#include "memdebug.h"
31
32
static bool chunk_is_empty(const struct buf_chunk *chunk)
33
{
34
return chunk->r_offset >= chunk->w_offset;
35
}
36
37
static bool chunk_is_full(const struct buf_chunk *chunk)
38
{
39
return chunk->w_offset >= chunk->dlen;
40
}
41
42
static size_t chunk_len(const struct buf_chunk *chunk)
43
{
44
return chunk->w_offset - chunk->r_offset;
45
}
46
47
static void chunk_reset(struct buf_chunk *chunk)
48
{
49
chunk->next = NULL;
50
chunk->r_offset = chunk->w_offset = 0;
51
}
52
53
static size_t chunk_append(struct buf_chunk *chunk,
54
const unsigned char *buf, size_t len)
55
{
56
unsigned char *p = &chunk->x.data[chunk->w_offset];
57
size_t n = chunk->dlen - chunk->w_offset;
58
DEBUGASSERT(chunk->dlen >= chunk->w_offset);
59
if(n) {
60
n = CURLMIN(n, len);
61
memcpy(p, buf, n);
62
chunk->w_offset += n;
63
}
64
return n;
65
}
66
67
static size_t chunk_read(struct buf_chunk *chunk,
68
unsigned char *buf, size_t len)
69
{
70
unsigned char *p = &chunk->x.data[chunk->r_offset];
71
size_t n = chunk->w_offset - chunk->r_offset;
72
DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
73
if(!n) {
74
return 0;
75
}
76
else if(n <= len) {
77
memcpy(buf, p, n);
78
chunk->r_offset = chunk->w_offset = 0;
79
return n;
80
}
81
else {
82
memcpy(buf, p, len);
83
chunk->r_offset += len;
84
return len;
85
}
86
}
87
88
static CURLcode chunk_slurpn(struct buf_chunk *chunk, size_t max_len,
89
Curl_bufq_reader *reader,
90
void *reader_ctx, size_t *pnread)
91
{
92
unsigned char *p = &chunk->x.data[chunk->w_offset];
93
size_t n = chunk->dlen - chunk->w_offset; /* free amount */
94
CURLcode result;
95
96
*pnread = 0;
97
DEBUGASSERT(chunk->dlen >= chunk->w_offset);
98
if(!n)
99
return CURLE_AGAIN;
100
if(max_len && n > max_len)
101
n = max_len;
102
result = reader(reader_ctx, p, n, pnread);
103
if(!result) {
104
DEBUGASSERT(*pnread <= n);
105
chunk->w_offset += *pnread;
106
}
107
return result;
108
}
109
110
static void chunk_peek(const struct buf_chunk *chunk,
111
const unsigned char **pbuf, size_t *plen)
112
{
113
DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
114
*pbuf = &chunk->x.data[chunk->r_offset];
115
*plen = chunk->w_offset - chunk->r_offset;
116
}
117
118
static void chunk_peek_at(const struct buf_chunk *chunk, size_t offset,
119
const unsigned char **pbuf, size_t *plen)
120
{
121
offset += chunk->r_offset;
122
DEBUGASSERT(chunk->w_offset >= offset);
123
*pbuf = &chunk->x.data[offset];
124
*plen = chunk->w_offset - offset;
125
}
126
127
static size_t chunk_skip(struct buf_chunk *chunk, size_t amount)
128
{
129
size_t n = chunk->w_offset - chunk->r_offset;
130
DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
131
if(n) {
132
n = CURLMIN(n, amount);
133
chunk->r_offset += n;
134
if(chunk->r_offset == chunk->w_offset)
135
chunk->r_offset = chunk->w_offset = 0;
136
}
137
return n;
138
}
139
140
static void chunk_list_free(struct buf_chunk **anchor)
141
{
142
struct buf_chunk *chunk;
143
while(*anchor) {
144
chunk = *anchor;
145
*anchor = chunk->next;
146
free(chunk);
147
}
148
}
149
150
151
void Curl_bufcp_init(struct bufc_pool *pool,
152
size_t chunk_size, size_t spare_max)
153
{
154
DEBUGASSERT(chunk_size > 0);
155
DEBUGASSERT(spare_max > 0);
156
memset(pool, 0, sizeof(*pool));
157
pool->chunk_size = chunk_size;
158
pool->spare_max = spare_max;
159
}
160
161
static CURLcode bufcp_take(struct bufc_pool *pool,
162
struct buf_chunk **pchunk)
163
{
164
struct buf_chunk *chunk = NULL;
165
166
if(pool->spare) {
167
chunk = pool->spare;
168
pool->spare = chunk->next;
169
--pool->spare_count;
170
chunk_reset(chunk);
171
*pchunk = chunk;
172
return CURLE_OK;
173
}
174
175
/* Check for integer overflow before allocation */
176
if(pool->chunk_size > SIZE_MAX - sizeof(*chunk)) {
177
*pchunk = NULL;
178
return CURLE_OUT_OF_MEMORY;
179
}
180
181
chunk = calloc(1, sizeof(*chunk) + pool->chunk_size);
182
if(!chunk) {
183
*pchunk = NULL;
184
return CURLE_OUT_OF_MEMORY;
185
}
186
chunk->dlen = pool->chunk_size;
187
*pchunk = chunk;
188
return CURLE_OK;
189
}
190
191
static void bufcp_put(struct bufc_pool *pool,
192
struct buf_chunk *chunk)
193
{
194
if(pool->spare_count >= pool->spare_max) {
195
free(chunk);
196
}
197
else {
198
chunk_reset(chunk);
199
chunk->next = pool->spare;
200
pool->spare = chunk;
201
++pool->spare_count;
202
}
203
}
204
205
void Curl_bufcp_free(struct bufc_pool *pool)
206
{
207
chunk_list_free(&pool->spare);
208
pool->spare_count = 0;
209
}
210
211
static void bufq_init(struct bufq *q, struct bufc_pool *pool,
212
size_t chunk_size, size_t max_chunks, int opts)
213
{
214
DEBUGASSERT(chunk_size > 0);
215
DEBUGASSERT(max_chunks > 0);
216
memset(q, 0, sizeof(*q));
217
q->chunk_size = chunk_size;
218
q->max_chunks = max_chunks;
219
q->pool = pool;
220
q->opts = opts;
221
}
222
223
void Curl_bufq_init2(struct bufq *q, size_t chunk_size, size_t max_chunks,
224
int opts)
225
{
226
bufq_init(q, NULL, chunk_size, max_chunks, opts);
227
}
228
229
void Curl_bufq_init(struct bufq *q, size_t chunk_size, size_t max_chunks)
230
{
231
bufq_init(q, NULL, chunk_size, max_chunks, BUFQ_OPT_NONE);
232
}
233
234
void Curl_bufq_initp(struct bufq *q, struct bufc_pool *pool,
235
size_t max_chunks, int opts)
236
{
237
bufq_init(q, pool, pool->chunk_size, max_chunks, opts);
238
}
239
240
void Curl_bufq_free(struct bufq *q)
241
{
242
chunk_list_free(&q->head);
243
chunk_list_free(&q->spare);
244
q->tail = NULL;
245
q->chunk_count = 0;
246
}
247
248
void Curl_bufq_reset(struct bufq *q)
249
{
250
struct buf_chunk *chunk;
251
while(q->head) {
252
chunk = q->head;
253
q->head = chunk->next;
254
chunk->next = q->spare;
255
q->spare = chunk;
256
}
257
q->tail = NULL;
258
}
259
260
size_t Curl_bufq_len(const struct bufq *q)
261
{
262
const struct buf_chunk *chunk = q->head;
263
size_t len = 0;
264
while(chunk) {
265
len += chunk_len(chunk);
266
chunk = chunk->next;
267
}
268
return len;
269
}
270
271
bool Curl_bufq_is_empty(const struct bufq *q)
272
{
273
return !q->head || chunk_is_empty(q->head);
274
}
275
276
bool Curl_bufq_is_full(const struct bufq *q)
277
{
278
if(!q->tail || q->spare)
279
return FALSE;
280
if(q->chunk_count < q->max_chunks)
281
return FALSE;
282
if(q->chunk_count > q->max_chunks)
283
return TRUE;
284
/* we have no spares and cannot make more, is the tail full? */
285
return chunk_is_full(q->tail);
286
}
287
288
static struct buf_chunk *get_spare(struct bufq *q)
289
{
290
struct buf_chunk *chunk = NULL;
291
292
if(q->spare) {
293
chunk = q->spare;
294
q->spare = chunk->next;
295
chunk_reset(chunk);
296
return chunk;
297
}
298
299
if(q->chunk_count >= q->max_chunks && (!(q->opts & BUFQ_OPT_SOFT_LIMIT)))
300
return NULL;
301
302
if(q->pool) {
303
if(bufcp_take(q->pool, &chunk))
304
return NULL;
305
++q->chunk_count;
306
return chunk;
307
}
308
else {
309
/* Check for integer overflow before allocation */
310
if(q->chunk_size > SIZE_MAX - sizeof(*chunk)) {
311
return NULL;
312
}
313
314
chunk = calloc(1, sizeof(*chunk) + q->chunk_size);
315
if(!chunk)
316
return NULL;
317
chunk->dlen = q->chunk_size;
318
++q->chunk_count;
319
return chunk;
320
}
321
}
322
323
static void prune_head(struct bufq *q)
324
{
325
struct buf_chunk *chunk;
326
327
while(q->head && chunk_is_empty(q->head)) {
328
chunk = q->head;
329
q->head = chunk->next;
330
if(q->tail == chunk)
331
q->tail = q->head;
332
if(q->pool) {
333
bufcp_put(q->pool, chunk);
334
--q->chunk_count;
335
}
336
else if((q->chunk_count > q->max_chunks) ||
337
(q->opts & BUFQ_OPT_NO_SPARES)) {
338
/* SOFT_LIMIT allowed us more than max. free spares until
339
* we are at max again. Or free them if we are configured
340
* to not use spares. */
341
free(chunk);
342
--q->chunk_count;
343
}
344
else {
345
chunk->next = q->spare;
346
q->spare = chunk;
347
}
348
}
349
}
350
351
static struct buf_chunk *get_non_full_tail(struct bufq *q)
352
{
353
struct buf_chunk *chunk;
354
355
if(q->tail && !chunk_is_full(q->tail))
356
return q->tail;
357
chunk = get_spare(q);
358
if(chunk) {
359
/* new tail, and possibly new head */
360
if(q->tail) {
361
q->tail->next = chunk;
362
q->tail = chunk;
363
}
364
else {
365
DEBUGASSERT(!q->head);
366
q->head = q->tail = chunk;
367
}
368
}
369
return chunk;
370
}
371
372
CURLcode Curl_bufq_write(struct bufq *q,
373
const unsigned char *buf, size_t len,
374
size_t *pnwritten)
375
{
376
struct buf_chunk *tail;
377
size_t n;
378
379
DEBUGASSERT(q->max_chunks > 0);
380
*pnwritten = 0;
381
while(len) {
382
tail = get_non_full_tail(q);
383
if(!tail) {
384
if((q->chunk_count < q->max_chunks) || (q->opts & BUFQ_OPT_SOFT_LIMIT))
385
/* should have gotten a tail, but did not */
386
return CURLE_OUT_OF_MEMORY;
387
break;
388
}
389
n = chunk_append(tail, buf, len);
390
if(!n)
391
break;
392
*pnwritten += n;
393
buf += n;
394
len -= n;
395
}
396
return (!*pnwritten && len) ? CURLE_AGAIN : CURLE_OK;
397
}
398
399
CURLcode Curl_bufq_cwrite(struct bufq *q,
400
const char *buf, size_t len,
401
size_t *pnwritten)
402
{
403
return Curl_bufq_write(q, (const unsigned char *)buf, len, pnwritten);
404
}
405
406
CURLcode Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len,
407
size_t *pnread)
408
{
409
*pnread = 0;
410
while(len && q->head) {
411
size_t n = chunk_read(q->head, buf, len);
412
if(n) {
413
*pnread += n;
414
buf += n;
415
len -= n;
416
}
417
prune_head(q);
418
}
419
return (!*pnread) ? CURLE_AGAIN : CURLE_OK;
420
}
421
422
CURLcode Curl_bufq_cread(struct bufq *q, char *buf, size_t len,
423
size_t *pnread)
424
{
425
return Curl_bufq_read(q, (unsigned char *)buf, len, pnread);
426
}
427
428
bool Curl_bufq_peek(struct bufq *q,
429
const unsigned char **pbuf, size_t *plen)
430
{
431
if(q->head && chunk_is_empty(q->head)) {
432
prune_head(q);
433
}
434
if(q->head && !chunk_is_empty(q->head)) {
435
chunk_peek(q->head, pbuf, plen);
436
return TRUE;
437
}
438
*pbuf = NULL;
439
*plen = 0;
440
return FALSE;
441
}
442
443
bool Curl_bufq_peek_at(struct bufq *q, size_t offset,
444
const unsigned char **pbuf, size_t *plen)
445
{
446
struct buf_chunk *c = q->head;
447
size_t clen;
448
449
while(c) {
450
clen = chunk_len(c);
451
if(!clen)
452
break;
453
if(offset >= clen) {
454
offset -= clen;
455
c = c->next;
456
continue;
457
}
458
chunk_peek_at(c, offset, pbuf, plen);
459
return TRUE;
460
}
461
*pbuf = NULL;
462
*plen = 0;
463
return FALSE;
464
}
465
466
void Curl_bufq_skip(struct bufq *q, size_t amount)
467
{
468
size_t n;
469
470
while(amount && q->head) {
471
n = chunk_skip(q->head, amount);
472
amount -= n;
473
prune_head(q);
474
}
475
}
476
477
CURLcode Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer,
478
void *writer_ctx, size_t *pwritten)
479
{
480
const unsigned char *buf;
481
size_t blen;
482
CURLcode result = CURLE_OK;
483
484
*pwritten = 0;
485
while(Curl_bufq_peek(q, &buf, &blen)) {
486
size_t chunk_written;
487
488
result = writer(writer_ctx, buf, blen, &chunk_written);
489
if(result) {
490
if((result == CURLE_AGAIN) && *pwritten) {
491
/* blocked on subsequent write, report success */
492
result = CURLE_OK;
493
}
494
break;
495
}
496
if(!chunk_written) {
497
if(!*pwritten) {
498
/* treat as blocked */
499
result = CURLE_AGAIN;
500
}
501
break;
502
}
503
*pwritten += chunk_written;
504
Curl_bufq_skip(q, chunk_written);
505
}
506
return result;
507
}
508
509
CURLcode Curl_bufq_write_pass(struct bufq *q,
510
const unsigned char *buf, size_t len,
511
Curl_bufq_writer *writer, void *writer_ctx,
512
size_t *pwritten)
513
{
514
CURLcode result = CURLE_OK;
515
size_t n;
516
517
*pwritten = 0;
518
while(len) {
519
if(Curl_bufq_is_full(q)) {
520
/* try to make room in case we are full */
521
result = Curl_bufq_pass(q, writer, writer_ctx, &n);
522
if(result) {
523
if(result != CURLE_AGAIN) {
524
/* real error, fail */
525
return result;
526
}
527
/* would block, bufq is full, give up */
528
break;
529
}
530
}
531
532
/* Add to bufq as much as there is room for */
533
result = Curl_bufq_write(q, buf, len, &n);
534
if(result) {
535
if(result != CURLE_AGAIN)
536
/* real error, fail */
537
return result;
538
/* result == CURLE_AGAIN */
539
if(*pwritten)
540
/* we did write successfully before */
541
result = CURLE_OK;
542
return result;
543
}
544
else if(n == 0)
545
/* edge case of writer returning 0 (and len is >0)
546
* break or we might enter an infinite loop here */
547
break;
548
549
/* Track what we added to bufq */
550
buf += n;
551
len -= n;
552
*pwritten += n;
553
}
554
555
return (!*pwritten && len) ? CURLE_AGAIN : CURLE_OK;
556
}
557
558
CURLcode Curl_bufq_sipn(struct bufq *q, size_t max_len,
559
Curl_bufq_reader *reader, void *reader_ctx,
560
size_t *pnread)
561
{
562
struct buf_chunk *tail = NULL;
563
564
*pnread = 0;
565
tail = get_non_full_tail(q);
566
if(!tail) {
567
if(q->chunk_count < q->max_chunks)
568
return CURLE_OUT_OF_MEMORY;
569
/* full, blocked */
570
return CURLE_AGAIN;
571
}
572
573
return chunk_slurpn(tail, max_len, reader, reader_ctx, pnread);
574
}
575
576
/**
577
* Read up to `max_len` bytes and append it to the end of the buffer queue.
578
* if `max_len` is 0, no limit is imposed and the call behaves exactly
579
* the same as `Curl_bufq_slurp()`.
580
* Returns the total amount of buf read (may be 0) in `pnread` or error
581
* Note that even in case of an error chunks may have been read and
582
* the buffer queue will have different length than before.
583
*/
584
static CURLcode bufq_slurpn(struct bufq *q, size_t max_len,
585
Curl_bufq_reader *reader, void *reader_ctx,
586
size_t *pnread)
587
{
588
CURLcode result;
589
590
*pnread = 0;
591
while(1) {
592
size_t n;
593
result = Curl_bufq_sipn(q, max_len, reader, reader_ctx, &n);
594
if(result) {
595
if(!*pnread || result != CURLE_AGAIN) {
596
/* blocked on first read or real error, fail */
597
return result;
598
}
599
result = CURLE_OK;
600
break;
601
}
602
else if(n == 0) {
603
/* eof, result remains CURLE_OK */
604
break;
605
}
606
*pnread += n;
607
if(max_len) {
608
DEBUGASSERT(n <= max_len);
609
max_len -= n;
610
if(!max_len)
611
break;
612
}
613
/* give up slurping when we get less bytes than we asked for */
614
if(q->tail && !chunk_is_full(q->tail))
615
break;
616
}
617
return result;
618
}
619
620
CURLcode Curl_bufq_slurp(struct bufq *q, Curl_bufq_reader *reader,
621
void *reader_ctx, size_t *pnread)
622
{
623
return bufq_slurpn(q, 0, reader, reader_ctx, pnread);
624
}
625
626