Coverage Report

Created: 2024-02-25 06:14

/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 size_t chunk_space(const struct buf_chunk *chunk)
49
0
{
50
0
  return chunk->dlen - chunk->w_offset;
51
0
}
52
53
static void chunk_reset(struct buf_chunk *chunk)
54
0
{
55
0
  chunk->next = NULL;
56
0
  chunk->r_offset = chunk->w_offset = 0;
57
0
}
58
59
static size_t chunk_append(struct buf_chunk *chunk,
60
                           const unsigned char *buf, size_t len)
61
0
{
62
0
  unsigned char *p = &chunk->x.data[chunk->w_offset];
63
0
  size_t n = chunk->dlen - chunk->w_offset;
64
0
  DEBUGASSERT(chunk->dlen >= chunk->w_offset);
65
0
  if(n) {
66
0
    n = CURLMIN(n, len);
67
0
    memcpy(p, buf, n);
68
0
    chunk->w_offset += n;
69
0
  }
70
0
  return n;
71
0
}
72
73
static size_t chunk_read(struct buf_chunk *chunk,
74
                         unsigned char *buf, size_t len)
75
0
{
76
0
  unsigned char *p = &chunk->x.data[chunk->r_offset];
77
0
  size_t n = chunk->w_offset - chunk->r_offset;
78
0
  DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
79
0
  if(!n) {
80
0
    return 0;
81
0
  }
82
0
  else if(n <= len) {
83
0
    memcpy(buf, p, n);
84
0
    chunk->r_offset = chunk->w_offset = 0;
85
0
    return n;
86
0
  }
87
0
  else {
88
0
    memcpy(buf, p, len);
89
0
    chunk->r_offset += len;
90
0
    return len;
91
0
  }
92
0
}
93
94
static ssize_t chunk_slurpn(struct buf_chunk *chunk, size_t max_len,
95
                            Curl_bufq_reader *reader,
96
                            void *reader_ctx, CURLcode *err)
97
0
{
98
0
  unsigned char *p = &chunk->x.data[chunk->w_offset];
99
0
  size_t n = chunk->dlen - chunk->w_offset; /* free amount */
100
0
  ssize_t nread;
101
102
0
  DEBUGASSERT(chunk->dlen >= chunk->w_offset);
103
0
  if(!n) {
104
0
    *err = CURLE_AGAIN;
105
0
    return -1;
106
0
  }
107
0
  if(max_len && n > max_len)
108
0
    n = max_len;
109
0
  nread = reader(reader_ctx, p, n, err);
110
0
  if(nread > 0) {
111
0
    DEBUGASSERT((size_t)nread <= n);
112
0
    chunk->w_offset += nread;
113
0
  }
114
0
  return nread;
115
0
}
116
117
static void chunk_peek(const struct buf_chunk *chunk,
118
                       const unsigned char **pbuf, size_t *plen)
119
0
{
120
0
  DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
121
0
  *pbuf = &chunk->x.data[chunk->r_offset];
122
0
  *plen = chunk->w_offset - chunk->r_offset;
123
0
}
124
125
static void chunk_peek_at(const struct buf_chunk *chunk, size_t offset,
126
                          const unsigned char **pbuf, size_t *plen)
127
0
{
128
0
  offset += chunk->r_offset;
129
0
  DEBUGASSERT(chunk->w_offset >= offset);
130
0
  *pbuf = &chunk->x.data[offset];
131
0
  *plen = chunk->w_offset - offset;
132
0
}
133
134
static size_t chunk_skip(struct buf_chunk *chunk, size_t amount)
135
0
{
136
0
  size_t n = chunk->w_offset - chunk->r_offset;
137
0
  DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
138
0
  if(n) {
139
0
    n = CURLMIN(n, amount);
140
0
    chunk->r_offset += n;
141
0
    if(chunk->r_offset == chunk->w_offset)
142
0
      chunk->r_offset = chunk->w_offset = 0;
143
0
  }
144
0
  return n;
145
0
}
146
147
static void chunk_list_free(struct buf_chunk **anchor)
148
0
{
149
0
  struct buf_chunk *chunk;
150
0
  while(*anchor) {
151
0
    chunk = *anchor;
152
0
    *anchor = chunk->next;
153
0
    free(chunk);
154
0
  }
155
0
}
156
157
158
159
void Curl_bufcp_init(struct bufc_pool *pool,
160
                     size_t chunk_size, size_t spare_max)
161
0
{
162
0
  DEBUGASSERT(chunk_size > 0);
163
0
  DEBUGASSERT(spare_max > 0);
164
0
  memset(pool, 0, sizeof(*pool));
165
0
  pool->chunk_size = chunk_size;
166
0
  pool->spare_max = spare_max;
167
0
}
168
169
static CURLcode bufcp_take(struct bufc_pool *pool,
170
                           struct buf_chunk **pchunk)
171
0
{
172
0
  struct buf_chunk *chunk = NULL;
173
174
0
  if(pool->spare) {
175
0
    chunk = pool->spare;
176
0
    pool->spare = chunk->next;
177
0
    --pool->spare_count;
178
0
    chunk_reset(chunk);
179
0
    *pchunk = chunk;
180
0
    return CURLE_OK;
181
0
  }
182
183
0
  chunk = calloc(1, sizeof(*chunk) + pool->chunk_size);
184
0
  if(!chunk) {
185
0
    *pchunk = NULL;
186
0
    return CURLE_OUT_OF_MEMORY;
187
0
  }
188
0
  chunk->dlen = pool->chunk_size;
189
0
  *pchunk = chunk;
190
0
  return CURLE_OK;
191
0
}
192
193
static void bufcp_put(struct bufc_pool *pool,
194
                      struct buf_chunk *chunk)
195
0
{
196
0
  if(pool->spare_count >= pool->spare_max) {
197
0
    free(chunk);
198
0
  }
199
0
  else {
200
0
    chunk_reset(chunk);
201
0
    chunk->next = pool->spare;
202
0
    pool->spare = chunk;
203
0
    ++pool->spare_count;
204
0
  }
205
0
}
206
207
void Curl_bufcp_free(struct bufc_pool *pool)
208
0
{
209
0
  chunk_list_free(&pool->spare);
210
0
  pool->spare_count = 0;
211
0
}
212
213
static void bufq_init(struct bufq *q, struct bufc_pool *pool,
214
                      size_t chunk_size, size_t max_chunks, int opts)
215
0
{
216
0
  DEBUGASSERT(chunk_size > 0);
217
0
  DEBUGASSERT(max_chunks > 0);
218
0
  memset(q, 0, sizeof(*q));
219
0
  q->chunk_size = chunk_size;
220
0
  q->max_chunks = max_chunks;
221
0
  q->pool = pool;
222
0
  q->opts = opts;
223
0
}
224
225
void Curl_bufq_init2(struct bufq *q, size_t chunk_size, size_t max_chunks,
226
                     int opts)
227
0
{
228
0
  bufq_init(q, NULL, chunk_size, max_chunks, opts);
229
0
}
230
231
void Curl_bufq_init(struct bufq *q, size_t chunk_size, size_t max_chunks)
232
0
{
233
0
  bufq_init(q, NULL, chunk_size, max_chunks, BUFQ_OPT_NONE);
234
0
}
235
236
void Curl_bufq_initp(struct bufq *q, struct bufc_pool *pool,
237
                     size_t max_chunks, int opts)
238
0
{
239
0
  bufq_init(q, pool, pool->chunk_size, max_chunks, opts);
240
0
}
241
242
void Curl_bufq_free(struct bufq *q)
243
0
{
244
0
  chunk_list_free(&q->head);
245
0
  chunk_list_free(&q->spare);
246
0
  q->tail = NULL;
247
0
  q->chunk_count = 0;
248
0
}
249
250
void Curl_bufq_reset(struct bufq *q)
251
0
{
252
0
  struct buf_chunk *chunk;
253
0
  while(q->head) {
254
0
    chunk = q->head;
255
0
    q->head = chunk->next;
256
0
    chunk->next = q->spare;
257
0
    q->spare = chunk;
258
0
  }
259
0
  q->tail = NULL;
260
0
}
261
262
size_t Curl_bufq_len(const struct bufq *q)
263
0
{
264
0
  const struct buf_chunk *chunk = q->head;
265
0
  size_t len = 0;
266
0
  while(chunk) {
267
0
    len += chunk_len(chunk);
268
0
    chunk = chunk->next;
269
0
  }
270
0
  return len;
271
0
}
272
273
size_t Curl_bufq_space(const struct bufq *q)
274
0
{
275
0
  size_t space = 0;
276
0
  if(q->tail)
277
0
    space += chunk_space(q->tail);
278
0
  if(q->spare) {
279
0
    struct buf_chunk *chunk = q->spare;
280
0
    while(chunk) {
281
0
      space += chunk->dlen;
282
0
      chunk = chunk->next;
283
0
    }
284
0
  }
285
0
  if(q->chunk_count < q->max_chunks) {
286
0
    space += (q->max_chunks - q->chunk_count) * q->chunk_size;
287
0
  }
288
0
  return space;
289
0
}
290
291
bool Curl_bufq_is_empty(const struct bufq *q)
292
0
{
293
0
  return !q->head || chunk_is_empty(q->head);
294
0
}
295
296
bool Curl_bufq_is_full(const struct bufq *q)
297
0
{
298
0
  if(!q->tail || q->spare)
299
0
    return FALSE;
300
0
  if(q->chunk_count < q->max_chunks)
301
0
    return FALSE;
302
0
  if(q->chunk_count > q->max_chunks)
303
0
    return TRUE;
304
  /* we have no spares and cannot make more, is the tail full? */
305
0
  return chunk_is_full(q->tail);
306
0
}
307
308
static struct buf_chunk *get_spare(struct bufq *q)
309
0
{
310
0
  struct buf_chunk *chunk = NULL;
311
312
0
  if(q->spare) {
313
0
    chunk = q->spare;
314
0
    q->spare = chunk->next;
315
0
    chunk_reset(chunk);
316
0
    return chunk;
317
0
  }
318
319
0
  if(q->chunk_count >= q->max_chunks && (!(q->opts & BUFQ_OPT_SOFT_LIMIT)))
320
0
    return NULL;
321
322
0
  if(q->pool) {
323
0
    if(bufcp_take(q->pool, &chunk))
324
0
      return NULL;
325
0
    ++q->chunk_count;
326
0
    return chunk;
327
0
  }
328
0
  else {
329
0
    chunk = calloc(1, sizeof(*chunk) + q->chunk_size);
330
0
    if(!chunk)
331
0
      return NULL;
332
0
    chunk->dlen = q->chunk_size;
333
0
    ++q->chunk_count;
334
0
    return chunk;
335
0
  }
336
0
}
337
338
static void prune_head(struct bufq *q)
339
0
{
340
0
  struct buf_chunk *chunk;
341
342
0
  while(q->head && chunk_is_empty(q->head)) {
343
0
    chunk = q->head;
344
0
    q->head = chunk->next;
345
0
    if(q->tail == chunk)
346
0
      q->tail = q->head;
347
0
    if(q->pool) {
348
0
      bufcp_put(q->pool, chunk);
349
0
      --q->chunk_count;
350
0
    }
351
0
    else if((q->chunk_count > q->max_chunks) ||
352
0
       (q->opts & BUFQ_OPT_NO_SPARES)) {
353
      /* SOFT_LIMIT allowed us more than max. free spares until
354
       * we are at max again. Or free them if we are configured
355
       * to not use spares. */
356
0
      free(chunk);
357
0
      --q->chunk_count;
358
0
    }
359
0
    else {
360
0
      chunk->next = q->spare;
361
0
      q->spare = chunk;
362
0
    }
363
0
  }
364
0
}
365
366
static struct buf_chunk *get_non_full_tail(struct bufq *q)
367
0
{
368
0
  struct buf_chunk *chunk;
369
370
0
  if(q->tail && !chunk_is_full(q->tail))
371
0
    return q->tail;
372
0
  chunk = get_spare(q);
373
0
  if(chunk) {
374
    /* new tail, and possibly new head */
375
0
    if(q->tail) {
376
0
      q->tail->next = chunk;
377
0
      q->tail = chunk;
378
0
    }
379
0
    else {
380
0
      DEBUGASSERT(!q->head);
381
0
      q->head = q->tail = chunk;
382
0
    }
383
0
  }
384
0
  return chunk;
385
0
}
386
387
ssize_t Curl_bufq_write(struct bufq *q,
388
                        const unsigned char *buf, size_t len,
389
                        CURLcode *err)
390
0
{
391
0
  struct buf_chunk *tail;
392
0
  ssize_t nwritten = 0;
393
0
  size_t n;
394
395
0
  DEBUGASSERT(q->max_chunks > 0);
396
0
  while(len) {
397
0
    tail = get_non_full_tail(q);
398
0
    if(!tail) {
399
0
      if(q->chunk_count < q->max_chunks) {
400
0
        *err = CURLE_OUT_OF_MEMORY;
401
0
        return -1;
402
0
      }
403
0
      break;
404
0
    }
405
0
    n = chunk_append(tail, buf, len);
406
0
    if(!n)
407
0
      break;
408
0
    nwritten += n;
409
0
    buf += n;
410
0
    len -= n;
411
0
  }
412
0
  if(nwritten == 0 && len) {
413
0
    *err = CURLE_AGAIN;
414
0
    return -1;
415
0
  }
416
0
  *err = CURLE_OK;
417
0
  return nwritten;
418
0
}
419
420
ssize_t Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len,
421
                       CURLcode *err)
422
0
{
423
0
  ssize_t nread = 0;
424
0
  size_t n;
425
426
0
  *err = CURLE_OK;
427
0
  while(len && q->head) {
428
0
    n = chunk_read(q->head, buf, len);
429
0
    if(n) {
430
0
      nread += n;
431
0
      buf += n;
432
0
      len -= n;
433
0
    }
434
0
    prune_head(q);
435
0
  }
436
0
  if(nread == 0) {
437
0
    *err = CURLE_AGAIN;
438
0
    return -1;
439
0
  }
440
0
  return nread;
441
0
}
442
443
bool Curl_bufq_peek(struct bufq *q,
444
                    const unsigned char **pbuf, size_t *plen)
445
0
{
446
0
  if(q->head && chunk_is_empty(q->head)) {
447
0
    prune_head(q);
448
0
  }
449
0
  if(q->head && !chunk_is_empty(q->head)) {
450
0
    chunk_peek(q->head, pbuf, plen);
451
0
    return TRUE;
452
0
  }
453
0
  *pbuf = NULL;
454
0
  *plen = 0;
455
0
  return FALSE;
456
0
}
457
458
bool Curl_bufq_peek_at(struct bufq *q, size_t offset,
459
                       const unsigned char **pbuf, size_t *plen)
460
0
{
461
0
  struct buf_chunk *c = q->head;
462
0
  size_t clen;
463
464
0
  while(c) {
465
0
    clen = chunk_len(c);
466
0
    if(!clen)
467
0
      break;
468
0
    if(offset >= clen) {
469
0
      offset -= clen;
470
0
      c = c->next;
471
0
      continue;
472
0
    }
473
0
    chunk_peek_at(c, offset, pbuf, plen);
474
0
    return TRUE;
475
0
  }
476
0
  *pbuf = NULL;
477
0
  *plen = 0;
478
0
  return FALSE;
479
0
}
480
481
void Curl_bufq_skip(struct bufq *q, size_t amount)
482
0
{
483
0
  size_t n;
484
485
0
  while(amount && q->head) {
486
0
    n = chunk_skip(q->head, amount);
487
0
    amount -= n;
488
0
    prune_head(q);
489
0
  }
490
0
}
491
492
ssize_t Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer,
493
                       void *writer_ctx, CURLcode *err)
494
0
{
495
0
  const unsigned char *buf;
496
0
  size_t blen;
497
0
  ssize_t nwritten = 0;
498
499
0
  while(Curl_bufq_peek(q, &buf, &blen)) {
500
0
    ssize_t chunk_written;
501
502
0
    chunk_written = writer(writer_ctx, buf, blen, err);
503
0
    if(chunk_written < 0) {
504
0
      if(!nwritten || *err != CURLE_AGAIN) {
505
        /* blocked on first write or real error, fail */
506
0
        nwritten = -1;
507
0
      }
508
0
      break;
509
0
    }
510
0
    if(!chunk_written) {
511
0
      if(!nwritten) {
512
        /* treat as blocked */
513
0
        *err = CURLE_AGAIN;
514
0
        nwritten = -1;
515
0
      }
516
0
      break;
517
0
    }
518
0
    Curl_bufq_skip(q, (size_t)chunk_written);
519
0
    nwritten += chunk_written;
520
0
  }
521
0
  return nwritten;
522
0
}
523
524
ssize_t Curl_bufq_write_pass(struct bufq *q,
525
                             const unsigned char *buf, size_t len,
526
                             Curl_bufq_writer *writer, void *writer_ctx,
527
                             CURLcode *err)
528
0
{
529
0
  ssize_t nwritten = 0, n;
530
531
0
  *err = CURLE_OK;
532
0
  while(len) {
533
0
    if(Curl_bufq_is_full(q)) {
534
      /* try to make room in case we are full */
535
0
      n = Curl_bufq_pass(q, writer, writer_ctx, err);
536
0
      if(n < 0) {
537
0
        if(*err != CURLE_AGAIN) {
538
          /* real error, fail */
539
0
          return -1;
540
0
        }
541
        /* would block, bufq is full, give up */
542
0
        break;
543
0
      }
544
0
    }
545
546
    /* Add whatever is remaining now to bufq */
547
0
    n = Curl_bufq_write(q, buf, len, err);
548
0
    if(n < 0) {
549
0
      if(*err != CURLE_AGAIN) {
550
        /* real error, fail */
551
0
        return -1;
552
0
      }
553
      /* no room in bufq */
554
0
      break;
555
0
    }
556
    /* edge case of writer returning 0 (and len is >0)
557
     * break or we might enter an infinite loop here */
558
0
    if(n == 0)
559
0
      break;
560
561
    /* Maybe only part of `data` has been added, continue to loop */
562
0
    buf += (size_t)n;
563
0
    len -= (size_t)n;
564
0
    nwritten += (size_t)n;
565
0
  }
566
567
0
  if(!nwritten && len) {
568
0
    *err = CURLE_AGAIN;
569
0
    return -1;
570
0
  }
571
0
  *err = CURLE_OK;
572
0
  return nwritten;
573
0
}
574
575
ssize_t Curl_bufq_sipn(struct bufq *q, size_t max_len,
576
                       Curl_bufq_reader *reader, void *reader_ctx,
577
                       CURLcode *err)
578
0
{
579
0
  struct buf_chunk *tail = NULL;
580
0
  ssize_t nread;
581
582
0
  *err = CURLE_AGAIN;
583
0
  tail = get_non_full_tail(q);
584
0
  if(!tail) {
585
0
    if(q->chunk_count < q->max_chunks) {
586
0
      *err = CURLE_OUT_OF_MEMORY;
587
0
      return -1;
588
0
    }
589
    /* full, blocked */
590
0
    *err = CURLE_AGAIN;
591
0
    return -1;
592
0
  }
593
594
0
  nread = chunk_slurpn(tail, max_len, reader, reader_ctx, err);
595
0
  if(nread < 0) {
596
0
    return -1;
597
0
  }
598
0
  else if(nread == 0) {
599
    /* eof */
600
0
    *err = CURLE_OK;
601
0
  }
602
0
  return nread;
603
0
}
604
605
/**
606
 * Read up to `max_len` bytes and append it to the end of the buffer queue.
607
 * if `max_len` is 0, no limit is imposed and the call behaves exactly
608
 * the same as `Curl_bufq_slurp()`.
609
 * Returns the total amount of buf read (may be 0) or -1 on other
610
 * reader errors.
611
 * Note that even in case of a -1 chunks may have been read and
612
 * the buffer queue will have different length than before.
613
 */
614
static ssize_t bufq_slurpn(struct bufq *q, size_t max_len,
615
                           Curl_bufq_reader *reader, void *reader_ctx,
616
                           CURLcode *err)
617
0
{
618
0
  ssize_t nread = 0, n;
619
620
0
  *err = CURLE_AGAIN;
621
0
  while(1) {
622
623
0
    n = Curl_bufq_sipn(q, max_len, reader, reader_ctx, err);
624
0
    if(n < 0) {
625
0
      if(!nread || *err != CURLE_AGAIN) {
626
        /* blocked on first read or real error, fail */
627
0
        nread = -1;
628
0
      }
629
0
      else
630
0
        *err = CURLE_OK;
631
0
      break;
632
0
    }
633
0
    else if(n == 0) {
634
      /* eof */
635
0
      *err = CURLE_OK;
636
0
      break;
637
0
    }
638
0
    nread += (size_t)n;
639
0
    if(max_len) {
640
0
      DEBUGASSERT((size_t)n <= max_len);
641
0
      max_len -= (size_t)n;
642
0
      if(!max_len)
643
0
        break;
644
0
    }
645
    /* give up slurping when we get less bytes than we asked for */
646
0
    if(q->tail && !chunk_is_full(q->tail))
647
0
      break;
648
0
  }
649
0
  return nread;
650
0
}
651
652
ssize_t Curl_bufq_slurp(struct bufq *q, Curl_bufq_reader *reader,
653
                        void *reader_ctx, CURLcode *err)
654
0
{
655
0
  return bufq_slurpn(q, 0, reader, reader_ctx, err);
656
0
}