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