Coverage Report

Created: 2025-07-11 06:33

/src/PROJ/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
0
{
35
0
  return chunk->r_offset >= chunk->w_offset;
36
0
}
37
38
static bool chunk_is_full(const struct buf_chunk *chunk)
39
0
{
40
0
  return chunk->w_offset >= chunk->dlen;
41
0
}
42
43
static size_t chunk_len(const struct buf_chunk *chunk)
44
0
{
45
0
  return chunk->w_offset - chunk->r_offset;
46
0
}
47
48
static void chunk_reset(struct buf_chunk *chunk)
49
0
{
50
0
  chunk->next = NULL;
51
0
  chunk->r_offset = chunk->w_offset = 0;
52
0
}
53
54
static size_t chunk_append(struct buf_chunk *chunk,
55
                           const unsigned char *buf, size_t len)
56
0
{
57
0
  unsigned char *p = &chunk->x.data[chunk->w_offset];
58
0
  size_t n = chunk->dlen - chunk->w_offset;
59
0
  DEBUGASSERT(chunk->dlen >= chunk->w_offset);
60
0
  if(n) {
61
0
    n = CURLMIN(n, len);
62
0
    memcpy(p, buf, n);
63
0
    chunk->w_offset += n;
64
0
  }
65
0
  return n;
66
0
}
67
68
static size_t chunk_read(struct buf_chunk *chunk,
69
                         unsigned char *buf, size_t len)
70
0
{
71
0
  unsigned char *p = &chunk->x.data[chunk->r_offset];
72
0
  size_t n = chunk->w_offset - chunk->r_offset;
73
0
  DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
74
0
  if(!n) {
75
0
    return 0;
76
0
  }
77
0
  else if(n <= len) {
78
0
    memcpy(buf, p, n);
79
0
    chunk->r_offset = chunk->w_offset = 0;
80
0
    return n;
81
0
  }
82
0
  else {
83
0
    memcpy(buf, p, len);
84
0
    chunk->r_offset += len;
85
0
    return len;
86
0
  }
87
0
}
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
0
{
93
0
  unsigned char *p = &chunk->x.data[chunk->w_offset];
94
0
  size_t n = chunk->dlen - chunk->w_offset; /* free amount */
95
0
  CURLcode result;
96
97
0
  *pnread = 0;
98
0
  DEBUGASSERT(chunk->dlen >= chunk->w_offset);
99
0
  if(!n)
100
0
    return CURLE_AGAIN;
101
0
  if(max_len && n > max_len)
102
0
    n = max_len;
103
0
  result = reader(reader_ctx, p, n, pnread);
104
0
  if(!result) {
105
0
    DEBUGASSERT(*pnread <= n);
106
0
    chunk->w_offset += *pnread;
107
0
  }
108
0
  return result;
109
0
}
110
111
static void chunk_peek(const struct buf_chunk *chunk,
112
                       const unsigned char **pbuf, size_t *plen)
113
0
{
114
0
  DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
115
0
  *pbuf = &chunk->x.data[chunk->r_offset];
116
0
  *plen = chunk->w_offset - chunk->r_offset;
117
0
}
118
119
static void chunk_peek_at(const struct buf_chunk *chunk, size_t offset,
120
                          const unsigned char **pbuf, size_t *plen)
121
0
{
122
0
  offset += chunk->r_offset;
123
0
  DEBUGASSERT(chunk->w_offset >= offset);
124
0
  *pbuf = &chunk->x.data[offset];
125
0
  *plen = chunk->w_offset - offset;
126
0
}
127
128
static size_t chunk_skip(struct buf_chunk *chunk, size_t amount)
129
0
{
130
0
  size_t n = chunk->w_offset - chunk->r_offset;
131
0
  DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
132
0
  if(n) {
133
0
    n = CURLMIN(n, amount);
134
0
    chunk->r_offset += n;
135
0
    if(chunk->r_offset == chunk->w_offset)
136
0
      chunk->r_offset = chunk->w_offset = 0;
137
0
  }
138
0
  return n;
139
0
}
140
141
static void chunk_list_free(struct buf_chunk **anchor)
142
0
{
143
0
  struct buf_chunk *chunk;
144
0
  while(*anchor) {
145
0
    chunk = *anchor;
146
0
    *anchor = chunk->next;
147
0
    free(chunk);
148
0
  }
149
0
}
150
151
152
153
void Curl_bufcp_init(struct bufc_pool *pool,
154
                     size_t chunk_size, size_t spare_max)
155
0
{
156
0
  DEBUGASSERT(chunk_size > 0);
157
0
  DEBUGASSERT(spare_max > 0);
158
0
  memset(pool, 0, sizeof(*pool));
159
0
  pool->chunk_size = chunk_size;
160
0
  pool->spare_max = spare_max;
161
0
}
162
163
static CURLcode bufcp_take(struct bufc_pool *pool,
164
                           struct buf_chunk **pchunk)
165
0
{
166
0
  struct buf_chunk *chunk = NULL;
167
168
0
  if(pool->spare) {
169
0
    chunk = pool->spare;
170
0
    pool->spare = chunk->next;
171
0
    --pool->spare_count;
172
0
    chunk_reset(chunk);
173
0
    *pchunk = chunk;
174
0
    return CURLE_OK;
175
0
  }
176
177
0
  chunk = calloc(1, sizeof(*chunk) + pool->chunk_size);
178
0
  if(!chunk) {
179
0
    *pchunk = NULL;
180
0
    return CURLE_OUT_OF_MEMORY;
181
0
  }
182
0
  chunk->dlen = pool->chunk_size;
183
0
  *pchunk = chunk;
184
0
  return CURLE_OK;
185
0
}
186
187
static void bufcp_put(struct bufc_pool *pool,
188
                      struct buf_chunk *chunk)
189
0
{
190
0
  if(pool->spare_count >= pool->spare_max) {
191
0
    free(chunk);
192
0
  }
193
0
  else {
194
0
    chunk_reset(chunk);
195
0
    chunk->next = pool->spare;
196
0
    pool->spare = chunk;
197
0
    ++pool->spare_count;
198
0
  }
199
0
}
200
201
void Curl_bufcp_free(struct bufc_pool *pool)
202
0
{
203
0
  chunk_list_free(&pool->spare);
204
0
  pool->spare_count = 0;
205
0
}
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
0
{
210
0
  DEBUGASSERT(chunk_size > 0);
211
0
  DEBUGASSERT(max_chunks > 0);
212
0
  memset(q, 0, sizeof(*q));
213
0
  q->chunk_size = chunk_size;
214
0
  q->max_chunks = max_chunks;
215
0
  q->pool = pool;
216
0
  q->opts = opts;
217
0
}
218
219
void Curl_bufq_init2(struct bufq *q, size_t chunk_size, size_t max_chunks,
220
                     int opts)
221
0
{
222
0
  bufq_init(q, NULL, chunk_size, max_chunks, opts);
223
0
}
224
225
void Curl_bufq_init(struct bufq *q, size_t chunk_size, size_t max_chunks)
226
0
{
227
0
  bufq_init(q, NULL, chunk_size, max_chunks, BUFQ_OPT_NONE);
228
0
}
229
230
void Curl_bufq_initp(struct bufq *q, struct bufc_pool *pool,
231
                     size_t max_chunks, int opts)
232
0
{
233
0
  bufq_init(q, pool, pool->chunk_size, max_chunks, opts);
234
0
}
235
236
void Curl_bufq_free(struct bufq *q)
237
0
{
238
0
  chunk_list_free(&q->head);
239
0
  chunk_list_free(&q->spare);
240
0
  q->tail = NULL;
241
0
  q->chunk_count = 0;
242
0
}
243
244
void Curl_bufq_reset(struct bufq *q)
245
0
{
246
0
  struct buf_chunk *chunk;
247
0
  while(q->head) {
248
0
    chunk = q->head;
249
0
    q->head = chunk->next;
250
0
    chunk->next = q->spare;
251
0
    q->spare = chunk;
252
0
  }
253
0
  q->tail = NULL;
254
0
}
255
256
size_t Curl_bufq_len(const struct bufq *q)
257
0
{
258
0
  const struct buf_chunk *chunk = q->head;
259
0
  size_t len = 0;
260
0
  while(chunk) {
261
0
    len += chunk_len(chunk);
262
0
    chunk = chunk->next;
263
0
  }
264
0
  return len;
265
0
}
266
267
bool Curl_bufq_is_empty(const struct bufq *q)
268
0
{
269
0
  return !q->head || chunk_is_empty(q->head);
270
0
}
271
272
bool Curl_bufq_is_full(const struct bufq *q)
273
0
{
274
0
  if(!q->tail || q->spare)
275
0
    return FALSE;
276
0
  if(q->chunk_count < q->max_chunks)
277
0
    return FALSE;
278
0
  if(q->chunk_count > q->max_chunks)
279
0
    return TRUE;
280
  /* we have no spares and cannot make more, is the tail full? */
281
0
  return chunk_is_full(q->tail);
282
0
}
283
284
static struct buf_chunk *get_spare(struct bufq *q)
285
0
{
286
0
  struct buf_chunk *chunk = NULL;
287
288
0
  if(q->spare) {
289
0
    chunk = q->spare;
290
0
    q->spare = chunk->next;
291
0
    chunk_reset(chunk);
292
0
    return chunk;
293
0
  }
294
295
0
  if(q->chunk_count >= q->max_chunks && (!(q->opts & BUFQ_OPT_SOFT_LIMIT)))
296
0
    return NULL;
297
298
0
  if(q->pool) {
299
0
    if(bufcp_take(q->pool, &chunk))
300
0
      return NULL;
301
0
    ++q->chunk_count;
302
0
    return chunk;
303
0
  }
304
0
  else {
305
0
    chunk = calloc(1, sizeof(*chunk) + q->chunk_size);
306
0
    if(!chunk)
307
0
      return NULL;
308
0
    chunk->dlen = q->chunk_size;
309
0
    ++q->chunk_count;
310
0
    return chunk;
311
0
  }
312
0
}
313
314
static void prune_head(struct bufq *q)
315
0
{
316
0
  struct buf_chunk *chunk;
317
318
0
  while(q->head && chunk_is_empty(q->head)) {
319
0
    chunk = q->head;
320
0
    q->head = chunk->next;
321
0
    if(q->tail == chunk)
322
0
      q->tail = q->head;
323
0
    if(q->pool) {
324
0
      bufcp_put(q->pool, chunk);
325
0
      --q->chunk_count;
326
0
    }
327
0
    else if((q->chunk_count > q->max_chunks) ||
328
0
       (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
0
      free(chunk);
333
0
      --q->chunk_count;
334
0
    }
335
0
    else {
336
0
      chunk->next = q->spare;
337
0
      q->spare = chunk;
338
0
    }
339
0
  }
340
0
}
341
342
static struct buf_chunk *get_non_full_tail(struct bufq *q)
343
0
{
344
0
  struct buf_chunk *chunk;
345
346
0
  if(q->tail && !chunk_is_full(q->tail))
347
0
    return q->tail;
348
0
  chunk = get_spare(q);
349
0
  if(chunk) {
350
    /* new tail, and possibly new head */
351
0
    if(q->tail) {
352
0
      q->tail->next = chunk;
353
0
      q->tail = chunk;
354
0
    }
355
0
    else {
356
0
      DEBUGASSERT(!q->head);
357
0
      q->head = q->tail = chunk;
358
0
    }
359
0
  }
360
0
  return chunk;
361
0
}
362
363
CURLcode Curl_bufq_write(struct bufq *q,
364
                         const unsigned char *buf, size_t len,
365
                         size_t *pnwritten)
366
0
{
367
0
  struct buf_chunk *tail;
368
0
  size_t n;
369
370
0
  DEBUGASSERT(q->max_chunks > 0);
371
0
  *pnwritten = 0;
372
0
  while(len) {
373
0
    tail = get_non_full_tail(q);
374
0
    if(!tail) {
375
0
      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
0
      break;
379
0
    }
380
0
    n = chunk_append(tail, buf, len);
381
0
    if(!n)
382
0
      break;
383
0
    *pnwritten += n;
384
0
    buf += n;
385
0
    len -= n;
386
0
  }
387
0
  return (!*pnwritten && len) ? CURLE_AGAIN : CURLE_OK;
388
0
}
389
390
CURLcode Curl_bufq_cwrite(struct bufq *q,
391
                          const char *buf, size_t len,
392
                          size_t *pnwritten)
393
0
{
394
0
  return Curl_bufq_write(q, (const unsigned char *)buf, len, pnwritten);
395
0
}
396
397
CURLcode Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len,
398
                        size_t *pnread)
399
0
{
400
0
  *pnread = 0;
401
0
  while(len && q->head) {
402
0
    size_t n = chunk_read(q->head, buf, len);
403
0
    if(n) {
404
0
      *pnread += n;
405
0
      buf += n;
406
0
      len -= n;
407
0
    }
408
0
    prune_head(q);
409
0
  }
410
0
  return (!*pnread) ? CURLE_AGAIN : CURLE_OK;
411
0
}
412
413
CURLcode Curl_bufq_cread(struct bufq *q, char *buf, size_t len,
414
                         size_t *pnread)
415
0
{
416
0
  return Curl_bufq_read(q, (unsigned char *)buf, len, pnread);
417
0
}
418
419
bool Curl_bufq_peek(struct bufq *q,
420
                    const unsigned char **pbuf, size_t *plen)
421
0
{
422
0
  if(q->head && chunk_is_empty(q->head)) {
423
0
    prune_head(q);
424
0
  }
425
0
  if(q->head && !chunk_is_empty(q->head)) {
426
0
    chunk_peek(q->head, pbuf, plen);
427
0
    return TRUE;
428
0
  }
429
0
  *pbuf = NULL;
430
0
  *plen = 0;
431
0
  return FALSE;
432
0
}
433
434
bool Curl_bufq_peek_at(struct bufq *q, size_t offset,
435
                       const unsigned char **pbuf, size_t *plen)
436
0
{
437
0
  struct buf_chunk *c = q->head;
438
0
  size_t clen;
439
440
0
  while(c) {
441
0
    clen = chunk_len(c);
442
0
    if(!clen)
443
0
      break;
444
0
    if(offset >= clen) {
445
0
      offset -= clen;
446
0
      c = c->next;
447
0
      continue;
448
0
    }
449
0
    chunk_peek_at(c, offset, pbuf, plen);
450
0
    return TRUE;
451
0
  }
452
0
  *pbuf = NULL;
453
0
  *plen = 0;
454
0
  return FALSE;
455
0
}
456
457
void Curl_bufq_skip(struct bufq *q, size_t amount)
458
0
{
459
0
  size_t n;
460
461
0
  while(amount && q->head) {
462
0
    n = chunk_skip(q->head, amount);
463
0
    amount -= n;
464
0
    prune_head(q);
465
0
  }
466
0
}
467
468
CURLcode Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer,
469
                        void *writer_ctx, size_t *pwritten)
470
0
{
471
0
  const unsigned char *buf;
472
0
  size_t blen;
473
0
  CURLcode result = CURLE_OK;
474
475
0
  *pwritten = 0;
476
0
  while(Curl_bufq_peek(q, &buf, &blen)) {
477
0
    size_t chunk_written;
478
479
0
    result = writer(writer_ctx, buf, blen, &chunk_written);
480
0
    if(result) {
481
0
      if((result == CURLE_AGAIN) && *pwritten) {
482
        /* blocked on subsequent write, report success */
483
0
        result = CURLE_OK;
484
0
      }
485
0
      break;
486
0
    }
487
0
    if(!chunk_written) {
488
0
      if(!*pwritten) {
489
        /* treat as blocked */
490
0
        result = CURLE_AGAIN;
491
0
      }
492
0
      break;
493
0
    }
494
0
    *pwritten += chunk_written;
495
0
    Curl_bufq_skip(q, chunk_written);
496
0
  }
497
0
  return result;
498
0
}
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
0
{
505
0
  CURLcode result = CURLE_OK;
506
0
  size_t n;
507
508
0
  *pwritten = 0;
509
0
  while(len) {
510
0
    if(Curl_bufq_is_full(q)) {
511
      /* try to make room in case we are full */
512
0
      result = Curl_bufq_pass(q, writer, writer_ctx, &n);
513
0
      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
0
    }
522
523
    /* Add to bufq as much as there is room for */
524
0
    result = Curl_bufq_write(q, buf, len, &n);
525
0
    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
0
    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
0
    buf += n;
541
0
    len -= n;
542
0
    *pwritten += n;
543
0
  }
544
545
0
  return (!*pwritten && len) ? CURLE_AGAIN : CURLE_OK;
546
0
}
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
0
{
552
0
  struct buf_chunk *tail = NULL;
553
554
0
  *pnread = 0;
555
0
  tail = get_non_full_tail(q);
556
0
  if(!tail) {
557
0
    if(q->chunk_count < q->max_chunks)
558
0
      return CURLE_OUT_OF_MEMORY;
559
    /* full, blocked */
560
0
    return CURLE_AGAIN;
561
0
  }
562
563
0
  return chunk_slurpn(tail, max_len, reader, reader_ctx, pnread);
564
0
}
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
0
{
578
0
  CURLcode result;
579
580
0
  *pnread = 0;
581
0
  while(1) {
582
0
    size_t n;
583
0
    result = Curl_bufq_sipn(q, max_len, reader, reader_ctx, &n);
584
0
    if(result) {
585
0
      if(!*pnread || result != CURLE_AGAIN) {
586
        /* blocked on first read or real error, fail */
587
0
        return result;
588
0
      }
589
0
      result = CURLE_OK;
590
0
      break;
591
0
    }
592
0
    else if(n == 0) {
593
      /* eof */
594
0
      result = CURLE_OK;
595
0
      break;
596
0
    }
597
0
    *pnread += n;
598
0
    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
0
    if(q->tail && !chunk_is_full(q->tail))
606
0
      break;
607
0
  }
608
0
  return result;
609
0
}
610
611
CURLcode Curl_bufq_slurp(struct bufq *q, Curl_bufq_reader *reader,
612
                         void *reader_ctx, size_t *pnread)
613
0
{
614
0
  return bufq_slurpn(q, 0, reader, reader_ctx, pnread);
615
0
}