Coverage Report

Created: 2025-07-12 06:36

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