1 #include "first.h" 2 3 /** 4 * the network chunk-API 5 * 6 * 7 */ 8 9 #include "chunk.h" 10 #include "fdevent.h" 11 #include "log.h" 12 13 #include <sys/types.h> 14 #include <sys/stat.h> 15 #include "sys-mmap.h" 16 #include "sys-setjmp.h" 17 18 #include <stdlib.h> 19 #include <fcntl.h> 20 #include <unistd.h> 21 22 #include <errno.h> 23 #include <string.h> 24 25 26 #ifdef HAVE_MMAP 27 28 #define MMAP_CHUNK_SIZE (512*1024) 29 30 __attribute_cold__ 31 /*__attribute_noinline__*/ 32 static off_t 33 mmap_pagemask (void) 34 { 35 #ifndef _WIN32 36 long pagesize = sysconf(_SC_PAGESIZE); 37 #else 38 long pagesize = -1; /*(not implemented (yet))*/ 39 #endif 40 if (-1 == pagesize) pagesize = 4096; 41 force_assert(pagesize < MMAP_CHUNK_SIZE); 42 return ~((off_t)pagesize - 1); /* pagesize always power-of-2 */ 43 } 44 45 #if 0 46 static off_t 47 mmap_align_offset (off_t start) 48 { 49 static off_t pagemask = 0; 50 if (0 == pagemask) 51 pagemask = mmap_pagemask(); 52 return (start & pagemask); 53 } 54 #endif 55 56 #define mmap_align_offset(offset) ((offset) & chunk_pagemask) 57 static off_t chunk_pagemask = 0; 58 static int chunk_mmap_flags = MAP_SHARED; 59 60 #endif /* HAVE_MMAP */ 61 62 63 /* default 1 MB */ 64 #define DEFAULT_TEMPFILE_SIZE (1 * 1024 * 1024) 65 66 static size_t chunk_buf_sz = 8192; 67 static chunk *chunks, *chunks_oversized, *chunks_filechunk; 68 static chunk *chunk_buffers; 69 static int chunks_oversized_n; 70 static const array *chunkqueue_default_tempdirs = NULL; 71 static off_t chunkqueue_default_tempfile_size = DEFAULT_TEMPFILE_SIZE; 72 73 void chunkqueue_set_chunk_size (size_t sz) 74 { 75 size_t x = 1024; 76 while (x < sz && x < (1u << 30)) x <<= 1; 77 chunk_buf_sz = sz > 0 ? x : 8192; 78 } 79 80 void chunkqueue_set_tempdirs_default_reset (void) 81 { 82 chunk_buf_sz = 8192; 83 chunkqueue_default_tempdirs = NULL; 84 chunkqueue_default_tempfile_size = DEFAULT_TEMPFILE_SIZE; 85 86 #ifdef HAVE_MMAP /*(abuse this func to initialize statics as startup)*/ 87 if (0 == chunk_pagemask) 88 chunk_pagemask = mmap_pagemask(); 89 chunk_mmap_flags = MAP_SHARED; 90 #endif 91 } 92 93 chunkqueue *chunkqueue_init(chunkqueue *cq) { 94 /* (if caller passes non-NULL cq, it must be 0-init) */ 95 if (NULL == cq) { 96 cq = calloc(1, sizeof(*cq)); 97 force_assert(NULL != cq); 98 } 99 100 cq->first = NULL; 101 cq->last = NULL; 102 103 cq->tempdirs = chunkqueue_default_tempdirs; 104 cq->upload_temp_file_size = chunkqueue_default_tempfile_size; 105 106 return cq; 107 } 108 109 __attribute_returns_nonnull__ 110 static chunk *chunk_init(void) { 111 chunk * const restrict c = calloc(1, sizeof(*c)); 112 force_assert(NULL != c); 113 114 #if 0 /*(zeroed by calloc())*/ 115 c->type = MEM_CHUNK; 116 c->next = NULL; 117 c->offset = 0; 118 c->file.length = 0; 119 c->file.is_temp = 0; 120 c->file.view = NULL; 121 #endif 122 c->file.fd = -1; 123 124 c->mem = buffer_init(); 125 return c; 126 } 127 128 __attribute_noinline__ 129 __attribute_returns_nonnull__ 130 static chunk *chunk_init_sz(size_t sz) { 131 chunk * const restrict c = chunk_init(); 132 buffer_string_prepare_copy(c->mem, sz-1); 133 return c; 134 } 135 136 #ifdef HAVE_MMAP 137 138 __attribute_malloc__ 139 __attribute_returns_nonnull__ 140 static void * chunk_file_view_init (void) { 141 chunk_file_view * const restrict cfv = calloc(1, sizeof(*cfv)); 142 force_assert(NULL != cfv); 143 cfv->mptr = MAP_FAILED; 144 #if 0 /*(zeroed by calloc())*/ 145 cfv->mlen = 0; 146 cfv->foff = 0; 147 #endif 148 cfv->refcnt = 1; 149 return cfv; 150 } 151 152 __attribute_nonnull__() 153 static chunk_file_view * chunk_file_view_release (chunk_file_view *cfv) { 154 if (0 == --cfv->refcnt) { 155 if (MAP_FAILED != cfv->mptr) 156 munmap(cfv->mptr, (size_t)cfv->mlen); 157 free(cfv); 158 } 159 return NULL; 160 } 161 162 __attribute_cold__ 163 __attribute_noinline__ 164 __attribute_nonnull__() 165 static chunk_file_view * chunk_file_view_failed (chunk_file_view *cfv) { 166 return chunk_file_view_release(cfv); 167 } 168 169 #endif /* HAVE_MMAP */ 170 171 ssize_t 172 chunk_file_pread (int fd, void *buf, size_t count, off_t offset) 173 { 174 /*(expects open file for FILE_CHUNK)*/ 175 #ifndef HAVE_PREAD 176 /*(On systems without pread() or equivalent, lseek() is repeated if this 177 * func is called in a loop, but this func is generally used on small files, 178 * or reading a small bit at a time. Even in the case of mod_deflate, files 179 * are not expected to be excessively large.) */ 180 if (-1 == lseek(fd, offset, SEEK_SET)) 181 return -1; 182 #endif 183 184 ssize_t rd; 185 do { 186 #ifdef HAVE_PREAD 187 rd =pread(fd, buf, count, offset); 188 #else 189 rd = read(fd, buf, count); 190 #endif 191 } while (-1 == rd && errno == EINTR); 192 return rd; 193 } 194 195 static void chunk_reset_file_chunk(chunk *c) { 196 if (c->file.is_temp) { 197 c->file.is_temp = 0; 198 if (!buffer_is_blank(c->mem)) 199 unlink(c->mem->ptr); 200 } 201 if (c->file.refchg) { 202 c->file.refchg(c->file.ref, -1); 203 c->file.refchg = 0; /* NULL fn ptr */ 204 c->file.ref = NULL; 205 } 206 else if (c->file.fd != -1) { 207 close(c->file.fd); 208 } 209 #ifdef HAVE_MMAP 210 if (c->file.view) 211 c->file.view = chunk_file_view_release(c->file.view); 212 #endif 213 c->file.fd = -1; 214 c->file.length = 0; 215 c->type = MEM_CHUNK; 216 } 217 218 static void chunk_reset(chunk *c) { 219 if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c); 220 221 buffer_clear(c->mem); 222 c->offset = 0; 223 } 224 225 static void chunk_free(chunk *c) { 226 if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c); 227 buffer_free(c->mem); 228 free(c); 229 } 230 231 static chunk * chunk_pop_oversized(size_t sz) { 232 /* future: might have buckets of certain sizes, up to socket buf sizes */ 233 if (chunks_oversized && chunks_oversized->mem->size >= sz) { 234 --chunks_oversized_n; 235 chunk *c = chunks_oversized; 236 chunks_oversized = c->next; 237 return c; 238 } 239 return NULL; 240 } 241 242 static void chunk_push_oversized(chunk * const c, const size_t sz) { 243 /* XXX: chunk_buffer_yield() may have removed need for list size limit */ 244 if (chunks_oversized_n < 64 && chunk_buf_sz >= 4096) { 245 ++chunks_oversized_n; 246 chunk **co = &chunks_oversized; 247 while (*co && sz < (*co)->mem->size) co = &(*co)->next; 248 c->next = *co; 249 *co = c; 250 } 251 else { 252 buffer * const tb = chunks_oversized ? chunks_oversized->mem : NULL; 253 if (tb && tb->size < sz) { 254 /* swap larger mem block onto head of list; free smaller mem */ 255 chunks_oversized->mem = c->mem; 256 c->mem = tb; 257 } 258 chunk_free(c); 259 } 260 } 261 262 __attribute_noinline__ 263 __attribute_returns_nonnull__ 264 static buffer * chunk_buffer_acquire_sz(const size_t sz) { 265 chunk *c; 266 buffer *b; 267 if (sz <= (chunk_buf_sz|1)) { 268 if (chunks) { 269 c = chunks; 270 chunks = c->next; 271 } 272 else 273 c = chunk_init_sz(chunk_buf_sz); 274 } 275 else { 276 c = chunk_pop_oversized(sz); 277 if (NULL == c) { 278 /*(round up to nearest chunk_buf_sz)*/ 279 /* NB: round down power-2 + 1 to avoid excess allocation 280 * (sz & ~1uL) relies on buffer_realloc() adding +1 *and* on callers 281 * of this func never passing power-2 + 1 sz unless direct caller 282 * adds +1 for '\0', as is done in chunk_buffer_prepare_append() */ 283 c = chunk_init_sz(((sz&~1uL)+(chunk_buf_sz-1)) & ~(chunk_buf_sz-1)); 284 } 285 } 286 c->next = chunk_buffers; 287 chunk_buffers = c; 288 b = c->mem; 289 c->mem = NULL; 290 return b; 291 } 292 293 buffer * chunk_buffer_acquire(void) { 294 return chunk_buffer_acquire_sz(chunk_buf_sz); 295 } 296 297 void chunk_buffer_release(buffer *b) { 298 if (NULL == b) return; 299 if (chunk_buffers) { 300 chunk *c = chunk_buffers; 301 chunk_buffers = c->next; 302 c->mem = b; 303 buffer_clear(b); 304 if (b->size == (chunk_buf_sz|1)) { 305 c->next = chunks; 306 chunks = c; 307 } 308 else if (b->size > chunk_buf_sz) 309 chunk_push_oversized(c, b->size); 310 else 311 chunk_free(c); 312 } 313 else { 314 buffer_free(b); 315 } 316 } 317 318 void chunk_buffer_yield(buffer *b) { 319 if (b->size == (chunk_buf_sz|1)) return; 320 321 buffer * const cb = chunk_buffer_acquire_sz(chunk_buf_sz); 322 buffer tb = *b; 323 *b = *cb; 324 *cb = tb; 325 chunk_buffer_release(cb); 326 } 327 328 size_t chunk_buffer_prepare_append(buffer * const b, size_t sz) { 329 if (sz > buffer_string_space(b)) { 330 sz += b->used ? b->used : 1; 331 buffer * const cb = chunk_buffer_acquire_sz(sz); 332 /* swap buffer contents and copy original b->ptr into larger b->ptr */ 333 /*(this does more than buffer_move())*/ 334 buffer tb = *b; 335 *b = *cb; 336 *cb = tb; 337 if ((b->used = tb.used)) 338 memcpy(b->ptr, tb.ptr, tb.used); 339 chunk_buffer_release(cb); 340 } 341 return buffer_string_space(b); 342 } 343 344 __attribute_noinline__ 345 __attribute_returns_nonnull__ 346 static chunk * chunk_acquire(size_t sz) { 347 if (sz <= (chunk_buf_sz|1)) { 348 if (chunks) { 349 chunk *c = chunks; 350 chunks = c->next; 351 return c; 352 } 353 sz = chunk_buf_sz; 354 } 355 else { 356 /*(round up to nearest chunk_buf_sz)*/ 357 sz = (sz + (chunk_buf_sz-1)) & ~(chunk_buf_sz-1); 358 chunk *c = chunk_pop_oversized(sz); 359 if (c) return c; 360 } 361 362 return chunk_init_sz(sz); 363 } 364 365 static void chunk_release(chunk *c) { 366 const size_t sz = c->mem->size; 367 if (sz == (chunk_buf_sz|1)) { 368 chunk_reset(c); 369 c->next = chunks; 370 chunks = c; 371 } 372 else if (sz > chunk_buf_sz) { 373 chunk_reset(c); 374 chunk_push_oversized(c, sz); 375 } 376 else if (c->type == FILE_CHUNK) { 377 chunk_reset(c); 378 c->next = chunks_filechunk; 379 chunks_filechunk = c; 380 } 381 else { 382 chunk_free(c); 383 } 384 } 385 386 __attribute_returns_nonnull__ 387 static chunk * chunk_acquire_filechunk(void) { 388 if (chunks_filechunk) { 389 chunk *c = chunks_filechunk; 390 chunks_filechunk = c->next; 391 return c; 392 } 393 return chunk_init(); 394 } 395 396 void chunkqueue_chunk_pool_clear(void) 397 { 398 for (chunk *next, *c = chunks; c; c = next) { 399 next = c->next; 400 chunk_free(c); 401 } 402 chunks = NULL; 403 for (chunk *next, *c = chunks_oversized; c; c = next) { 404 next = c->next; 405 chunk_free(c); 406 } 407 chunks_oversized = NULL; 408 chunks_oversized_n = 0; 409 for (chunk *next, *c = chunks_filechunk; c; c = next) { 410 next = c->next; 411 chunk_free(c); 412 } 413 chunks_filechunk = NULL; 414 } 415 416 void chunkqueue_chunk_pool_free(void) 417 { 418 chunkqueue_chunk_pool_clear(); 419 for (chunk *next, *c = chunk_buffers; c; c = next) { 420 next = c->next; 421 #if 1 /*(chunk_buffers contains MEM_CHUNK with (c->mem == NULL))*/ 422 free(c); 423 #else /*(c->mem = buffer_init() is no longer necessary below)*/ 424 c->mem = buffer_init(); /*(chunk_reset() expects c->mem != NULL)*/ 425 chunk_free(c); 426 #endif 427 } 428 chunk_buffers = NULL; 429 } 430 431 __attribute_pure__ 432 static off_t chunk_remaining_length(const chunk *c) { 433 /* MEM_CHUNK or FILE_CHUNK */ 434 return (c->type == MEM_CHUNK 435 ? (off_t)buffer_clen(c->mem) 436 : c->file.length) 437 - c->offset; 438 } 439 440 static void chunkqueue_release_chunks(chunkqueue *cq) { 441 cq->last = NULL; 442 for (chunk *c; (c = cq->first); ) { 443 cq->first = c->next; 444 chunk_release(c); 445 } 446 } 447 448 void chunkqueue_free(chunkqueue *cq) { 449 if (NULL == cq) return; 450 chunkqueue_release_chunks(cq); 451 free(cq); 452 } 453 454 static void chunkqueue_prepend_chunk(chunkqueue * const restrict cq, chunk * const restrict c) { 455 if (NULL == (c->next = cq->first)) cq->last = c; 456 cq->first = c; 457 } 458 459 static void chunkqueue_append_chunk(chunkqueue * const restrict cq, chunk * const restrict c) { 460 c->next = NULL; 461 *(cq->last ? &cq->last->next : &cq->first) = c; 462 cq->last = c; 463 } 464 465 __attribute_returns_nonnull__ 466 static chunk * chunkqueue_prepend_mem_chunk(chunkqueue *cq, size_t sz) { 467 chunk *c = chunk_acquire(sz); 468 chunkqueue_prepend_chunk(cq, c); 469 return c; 470 } 471 472 __attribute_returns_nonnull__ 473 static chunk * chunkqueue_append_mem_chunk(chunkqueue *cq, size_t sz) { 474 chunk *c = chunk_acquire(sz); 475 chunkqueue_append_chunk(cq, c); 476 return c; 477 } 478 479 __attribute_nonnull__() 480 __attribute_returns_nonnull__ 481 static chunk * chunkqueue_append_file_chunk(chunkqueue * const restrict cq, const buffer * const restrict fn, off_t offset, off_t len) { 482 chunk * const c = chunk_acquire_filechunk(); 483 chunkqueue_append_chunk(cq, c); 484 c->type = FILE_CHUNK; 485 c->offset = offset; 486 c->file.length = offset + len; 487 cq->bytes_in += len; 488 buffer_copy_buffer(c->mem, fn); 489 return c; 490 } 491 492 void chunkqueue_reset(chunkqueue *cq) { 493 chunkqueue_release_chunks(cq); 494 cq->bytes_in = 0; 495 cq->bytes_out = 0; 496 cq->tempdir_idx = 0; 497 } 498 499 void chunkqueue_append_file_fd(chunkqueue * const restrict cq, const buffer * const restrict fn, int fd, off_t offset, off_t len) { 500 if (len > 0) { 501 (chunkqueue_append_file_chunk(cq, fn, offset, len))->file.fd = fd; 502 } 503 else { 504 close(fd); 505 } 506 } 507 508 void chunkqueue_append_file(chunkqueue * const restrict cq, const buffer * const restrict fn, off_t offset, off_t len) { 509 if (len > 0) { 510 chunkqueue_append_file_chunk(cq, fn, offset, len); 511 } 512 } 513 514 515 static int chunkqueue_append_mem_extend_chunk(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) { 516 chunk *c = cq->last; 517 if (0 == len) return 1; 518 if (c != NULL && c->type == MEM_CHUNK 519 && buffer_string_space(c->mem) >= len) { 520 buffer_append_string_len(c->mem, mem, len); 521 cq->bytes_in += len; 522 return 1; 523 } 524 return 0; 525 } 526 527 528 void chunkqueue_append_buffer(chunkqueue * const restrict cq, buffer * const restrict mem) { 529 chunk *c; 530 const size_t len = buffer_clen(mem); 531 if (len < 1024 && chunkqueue_append_mem_extend_chunk(cq, mem->ptr, len)) { 532 buffer_clear(mem); 533 return; 534 } 535 536 c = chunkqueue_append_mem_chunk(cq, chunk_buf_sz); 537 cq->bytes_in += len; 538 buffer_move(c->mem, mem); 539 } 540 541 542 void chunkqueue_append_mem(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) { 543 chunk *c; 544 if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len)) 545 return; 546 547 c = chunkqueue_append_mem_chunk(cq, len+1); 548 cq->bytes_in += len; 549 buffer_copy_string_len(c->mem, mem, len); 550 } 551 552 553 void chunkqueue_append_mem_min(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) { 554 chunk *c; 555 if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len)) 556 return; 557 558 c = chunk_init_sz(len+1); 559 chunkqueue_append_chunk(cq, c); 560 cq->bytes_in += len; 561 buffer_copy_string_len(c->mem, mem, len); 562 } 563 564 565 void chunkqueue_append_chunkqueue(chunkqueue * const restrict cq, chunkqueue * const restrict src) { 566 if (NULL == src->first) return; 567 568 if (NULL == cq->first) { 569 cq->first = src->first; 570 } else { 571 cq->last->next = src->first; 572 } 573 cq->last = src->last; 574 cq->bytes_in += chunkqueue_length(src); 575 576 src->first = NULL; 577 src->last = NULL; 578 src->bytes_out = src->bytes_in; 579 } 580 581 582 buffer * chunkqueue_prepend_buffer_open_sz(chunkqueue *cq, size_t sz) { 583 chunk * const c = chunkqueue_prepend_mem_chunk(cq, sz); 584 return c->mem; 585 } 586 587 588 buffer * chunkqueue_prepend_buffer_open(chunkqueue *cq) { 589 return chunkqueue_prepend_buffer_open_sz(cq, chunk_buf_sz); 590 } 591 592 593 void chunkqueue_prepend_buffer_commit(chunkqueue *cq) { 594 cq->bytes_in += buffer_clen(cq->first->mem); 595 } 596 597 598 buffer * chunkqueue_append_buffer_open_sz(chunkqueue *cq, size_t sz) { 599 chunk * const c = chunkqueue_append_mem_chunk(cq, sz); 600 return c->mem; 601 } 602 603 604 buffer * chunkqueue_append_buffer_open(chunkqueue *cq) { 605 return chunkqueue_append_buffer_open_sz(cq, chunk_buf_sz); 606 } 607 608 609 void chunkqueue_append_buffer_commit(chunkqueue *cq) { 610 cq->bytes_in += buffer_clen(cq->last->mem); 611 } 612 613 614 char * chunkqueue_get_memory(chunkqueue * const restrict cq, size_t * const restrict len) { 615 size_t sz = *len ? *len : (chunk_buf_sz >> 1); 616 buffer *b; 617 chunk *c = cq->last; 618 if (NULL != c && MEM_CHUNK == c->type) { 619 /* return pointer into existing buffer if large enough */ 620 size_t avail = buffer_string_space(c->mem); 621 if (avail >= sz) { 622 *len = avail; 623 b = c->mem; 624 return b->ptr + buffer_clen(b); 625 } 626 } 627 628 /* allocate new chunk */ 629 b = chunkqueue_append_buffer_open_sz(cq, sz); 630 *len = buffer_string_space(b); 631 return b->ptr; 632 } 633 634 void chunkqueue_use_memory(chunkqueue * const restrict cq, chunk *ckpt, size_t len) { 635 buffer *b = cq->last->mem; 636 637 if (len > 0) { 638 buffer_commit(b, len); 639 cq->bytes_in += len; 640 if (cq->last == ckpt || NULL == ckpt || MEM_CHUNK != ckpt->type 641 || len > buffer_string_space(ckpt->mem)) return; 642 643 buffer_append_string_buffer(ckpt->mem, b); 644 } 645 else if (!buffer_is_blank(b)) { /*(cq->last == ckpt)*/ 646 return; /* last chunk is not empty */ 647 } 648 649 /* remove empty last chunk */ 650 chunk_release(cq->last); 651 cq->last = ckpt; 652 *(ckpt ? &ckpt->next : &cq->first) = NULL; 653 } 654 655 void chunkqueue_update_file(chunkqueue * const restrict cq, chunk *c, off_t len) { 656 /*assert(c->type == FILE_CHUNK);*/ 657 c->file.length += len; 658 cq->bytes_in += len; 659 if (0 == chunk_remaining_length(c)) 660 chunkqueue_remove_empty_chunks(cq); 661 } 662 663 void chunkqueue_set_tempdirs_default (const array *tempdirs, off_t upload_temp_file_size) { 664 if (upload_temp_file_size == 0) 665 upload_temp_file_size = DEFAULT_TEMPFILE_SIZE; 666 chunkqueue_default_tempdirs = tempdirs; 667 chunkqueue_default_tempfile_size = upload_temp_file_size; 668 } 669 670 void chunkqueue_set_tempdirs(chunkqueue * const restrict cq, const array * const restrict tempdirs, off_t upload_temp_file_size) { 671 if (upload_temp_file_size == 0) 672 upload_temp_file_size = chunkqueue_default_tempfile_size; 673 cq->tempdirs = tempdirs; 674 cq->upload_temp_file_size = upload_temp_file_size; 675 cq->tempdir_idx = 0; 676 } 677 678 __attribute_noinline__ 679 static void chunkqueue_dup_file_chunk_fd (chunk * const restrict d, const chunk * const restrict c) { 680 /*assert(d != c);*/ 681 /*assert(d->type == FILE_CHUNK);*/ 682 /*assert(c->type == FILE_CHUNK);*/ 683 if (c->file.fd >= 0) { 684 if (c->file.refchg) { 685 d->file.fd = c->file.fd; 686 d->file.ref = c->file.ref; 687 d->file.refchg = c->file.refchg; 688 d->file.refchg(d->file.ref, 1); 689 } 690 else 691 d->file.fd = fdevent_dup_cloexec(c->file.fd); 692 #ifdef HAVE_MMAP 693 if ((d->file.view = c->file.view)) 694 ++d->file.view->refcnt; 695 #endif 696 } 697 } 698 699 __attribute_noinline__ 700 static void chunkqueue_steal_partial_file_chunk(chunkqueue * const restrict dest, const chunk * const restrict c, const off_t len) { 701 chunkqueue_append_file(dest, c->mem, c->offset, len); 702 chunkqueue_dup_file_chunk_fd(dest->last, c); 703 } 704 705 void chunkqueue_steal(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len) { 706 /*(0-length first chunk (unexpected) is removed from src even if len == 0; 707 * progress is made when caller loops on this func)*/ 708 off_t clen; 709 do { 710 chunk * const c = src->first; 711 if (__builtin_expect( (NULL == c), 0)) break; 712 713 clen = chunk_remaining_length(c); 714 715 if (len >= clen) { 716 /* move complete chunk */ 717 src->first = c->next; 718 if (c == src->last) src->last = NULL; 719 720 if (__builtin_expect( (0 != clen), 1)) { 721 chunkqueue_append_chunk(dest, c); 722 dest->bytes_in += clen; 723 } 724 else /* drop empty chunk */ 725 chunk_release(c); 726 } else { 727 /* copy partial chunk */ 728 729 switch (c->type) { 730 case MEM_CHUNK: 731 chunkqueue_append_mem(dest, c->mem->ptr + c->offset, len); 732 break; 733 case FILE_CHUNK: 734 /* tempfile flag is in "last" chunk after the split */ 735 chunkqueue_steal_partial_file_chunk(dest, c, len); 736 break; 737 } 738 739 c->offset += len; 740 clen = len; 741 } 742 743 src->bytes_out += clen; 744 } while ((len -= clen)); 745 } 746 747 static int chunkqueue_get_append_mkstemp(buffer * const b, const char *path, const uint32_t len) { 748 buffer_copy_path_len2(b,path,len,CONST_STR_LEN("lighttpd-upload-XXXXXX")); 749 #if defined(HAVE_SPLICE) && defined(HAVE_PWRITE) 750 /*(splice() rejects O_APPEND target; omit flag if also using pwrite())*/ 751 return fdevent_mkostemp(b->ptr, 0); 752 #else 753 return fdevent_mkostemp(b->ptr, O_APPEND); 754 #endif 755 } 756 757 static chunk *chunkqueue_get_append_newtempfile(chunkqueue * const restrict cq, log_error_st * const restrict errh) { 758 static const buffer emptyb = { "", 0, 0 }; 759 chunk * const restrict last = cq->last; 760 chunk * const restrict c = chunkqueue_append_file_chunk(cq, &emptyb, 0, 0); 761 buffer * const restrict template = c->mem; 762 c->file.is_temp = 1; 763 764 if (cq->tempdirs && cq->tempdirs->used) { 765 /* we have several tempdirs, only if all of them fail we jump out */ 766 for (errno = EIO; cq->tempdir_idx < cq->tempdirs->used; ++cq->tempdir_idx) { 767 data_string *ds = (data_string *)cq->tempdirs->data[cq->tempdir_idx]; 768 c->file.fd = 769 chunkqueue_get_append_mkstemp(template, BUF_PTR_LEN(&ds->value)); 770 if (-1 != c->file.fd) return c; 771 } 772 } 773 else { 774 c->file.fd = 775 chunkqueue_get_append_mkstemp(template, CONST_STR_LEN("/var/tmp")); 776 if (-1 != c->file.fd) return c; 777 } 778 779 /* (report only last error to mkstemp() even if multiple temp dirs tried) */ 780 log_perror(errh, __FILE__, __LINE__, 781 "opening temp-file failed: %s", template->ptr); 782 /* remove (failed) final chunk */ 783 c->file.is_temp = 0; 784 if ((cq->last = last)) 785 last->next = NULL; 786 else 787 cq->first = NULL; 788 chunk_release(c); 789 return NULL; 790 } 791 792 static chunk *chunkqueue_get_append_tempfile(chunkqueue * const restrict cq, log_error_st * const restrict errh) { 793 /* 794 * if the last chunk is 795 * - smaller than cq->upload_temp_file_size 796 * -> append to it (and it then might exceed cq->upload_temp_file_size) 797 * otherwise 798 * -> create a new chunk 799 */ 800 801 chunk * const c = cq->last; 802 if (NULL != c && c->file.is_temp && c->file.fd >= 0) { 803 804 if (c->file.length < (off_t)cq->upload_temp_file_size) 805 return c; /* ok, take the last chunk for our job */ 806 807 /* the chunk is too large now, close it */ 808 force_assert(0 == c->file.refchg); /*(else should not happen)*/ 809 int rc = close(c->file.fd); 810 c->file.fd = -1; 811 if (0 != rc) { 812 log_perror(errh, __FILE__, __LINE__, 813 "close() temp-file %s failed", c->mem->ptr); 814 return NULL; 815 } 816 } 817 return chunkqueue_get_append_newtempfile(cq, errh); 818 } 819 820 __attribute_cold__ 821 static int chunkqueue_append_tempfile_err(chunkqueue * const cq, log_error_st * const restrict errh, chunk * const c) { 822 const int errnum = errno; 823 if (errnum == EINTR) return 1; /* retry */ 824 825 int retry = (errnum == ENOSPC && cq->tempdirs 826 && ++cq->tempdir_idx < cq->tempdirs->used); 827 if (!retry) 828 log_perror(errh, __FILE__, __LINE__, 829 "write() temp-file %s failed", c->mem->ptr); 830 831 if (0 == chunk_remaining_length(c)) { 832 /*(remove empty chunk and unlink tempfile)*/ 833 chunkqueue_remove_empty_chunks(cq); 834 } 835 else {/*(close tempfile; avoid later attempts to append)*/ 836 force_assert(0 == c->file.refchg); /*(else should not happen)*/ 837 int rc = close(c->file.fd); 838 c->file.fd = -1; 839 if (0 != rc) { 840 log_perror(errh, __FILE__, __LINE__, 841 "close() temp-file %s failed", c->mem->ptr); 842 retry = 0; 843 } 844 } 845 return retry; 846 } 847 848 __attribute_cold__ 849 __attribute_noinline__ 850 static int chunkqueue_to_tempfiles(chunkqueue * const restrict dest, log_error_st * const restrict errh) { 851 /* transfer chunks from dest to src, adjust dest->bytes_in, and then call 852 * chunkqueue_steal_with_tempfiles() to write chunks from src back into 853 * dest, but into tempfiles. chunkqueue_steal_with_tempfiles() calls back 854 * into chunkqueue_append_mem_to_tempfile(), but will not re-enter this func 855 * since chunks moved to src, and dest made empty before recursive call */ 856 const off_t cqlen = chunkqueue_length(dest); 857 chunkqueue src = *dest; /*(copy struct)*/ 858 dest->first = dest->last = NULL; 859 dest->bytes_in -= cqlen; 860 if (0 == chunkqueue_steal_with_tempfiles(dest, &src, cqlen, errh)) 861 return 0; 862 else { 863 const int errnum = errno; 864 chunkqueue_release_chunks(&src); 865 return -errnum; 866 } 867 } 868 869 int chunkqueue_append_mem_to_tempfile(chunkqueue * const restrict dest, const char * restrict mem, size_t len, log_error_st * const restrict errh) { 870 chunk *dst_c = dest->first; 871 872 /* check if prior MEM_CHUNK(s) exist and write to tempfile 873 * (check first chunk only, since if we are using tempfiles, then 874 * we expect further chunks to be tempfiles after starting tempfiles)*/ 875 if (dst_c && dst_c->type == MEM_CHUNK 876 && 0 != chunkqueue_to_tempfiles(dest, errh)) { 877 return -1; 878 } 879 880 do { 881 /*(aside: arg len is permitted to be 0 and creates tempfile as a 882 * side effect. This is used by mod_ssi for ssi exec, as the func 883 * chunkqueue_get_append_tempfile() is not public. The result is 884 * an empty chunk at the end of the chunkqueue, which typically 885 * should be avoided)*/ 886 dst_c = chunkqueue_get_append_tempfile(dest, errh); 887 if (NULL == dst_c) 888 return -1; 889 #ifdef __COVERITY__ 890 if (dst_c->file.fd < 0) return -1; 891 #endif 892 #ifdef HAVE_PWRITE 893 /* coverity[negative_returns : FALSE] */ 894 const ssize_t written =pwrite(dst_c->file.fd, mem, len, dst_c->file.length); 895 #else 896 /* coverity[negative_returns : FALSE] */ 897 const ssize_t written = write(dst_c->file.fd, mem, len); 898 #endif 899 900 if ((size_t) written == len) { 901 dst_c->file.length += len; 902 dest->bytes_in += len; 903 return 0; 904 } else if (written >= 0) { 905 /*(assume EINTR if partial write and retry write(); 906 * retry write() might fail with ENOSPC if no more space on volume)*/ 907 dest->bytes_in += written; 908 mem += written; 909 len -= (size_t)written; 910 dst_c->file.length += (size_t)written; 911 /* continue; retry */ 912 } else if (!chunkqueue_append_tempfile_err(dest, errh, dst_c)) { 913 break; /* return -1; */ 914 } /* else continue; retry */ 915 } while (len); 916 917 return -1; 918 } 919 920 #ifdef HAVE_PWRITEV 921 922 #ifdef HAVE_SYS_UIO_H 923 #include <sys/uio.h> 924 #endif 925 926 __attribute_cold__ 927 __attribute_noinline__ 928 static ssize_t chunkqueue_append_cqmem_to_tempfile_partial(chunkqueue * const dest, chunk * const c, ssize_t wr, log_error_st * const restrict errh) { 929 /* recover from partial write of existing dest MEM_CHUNK to tempfile */ 930 chunk *ckpt = dest->first; 931 while (ckpt->next != c) ckpt = ckpt->next; 932 ckpt->next = NULL; 933 dest->last = ckpt; 934 dest->bytes_in -= wr; /*(avoid double count in dest cq)*/ 935 dest->bytes_out -= wr; 936 chunkqueue_mark_written(dest, wr);/*(remove MEM_CHUNK written to tempfile)*/ 937 938 c->next = dest->first; /*(place tempfile at beginning of dest cq)*/ 939 dest->first = c; 940 return (0 == chunkqueue_to_tempfiles(dest, errh)) ? 0 : -1; 941 } 942 943 static ssize_t chunkqueue_append_cqmem_to_tempfile(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len, log_error_st * const restrict errh) { 944 /* write multiple MEM_CHUNKs to tempfile in single pwritev() syscall */ 945 /*(could lseek() and writev() if pwritev() is not available, 946 * but if writev() is available, pwritev() is likely available, 947 * e.g. any modern Linux or *BSD, and possibly anything not Windows)*/ 948 unsigned int iovcnt = 0; 949 struct iovec iov[16]; 950 951 off_t dlen = 0; 952 chunk *c; 953 for (c = dest->first; c && c->type == MEM_CHUNK; c = c->next) { 954 const off_t clen = chunk_remaining_length(c); 955 iov[iovcnt].iov_base = c->mem->ptr + c->offset; 956 iov[iovcnt].iov_len = (size_t)clen; 957 dlen += clen; 958 ++iovcnt; 959 if (__builtin_expect( (iovcnt == sizeof(iov)/sizeof(*iov)), 0)) 960 break; /*(not expecting large number of MEM_CHUNK)*/ 961 } 962 if (__builtin_expect( (c != NULL), 0) && dest->first->type == MEM_CHUNK) { 963 /*(expecting only MEM_CHUNK if dest cq starts w/ MEM_CHUNK)*/ 964 /*(use less efficient fallback if that assumption does not hold true)*/ 965 if (0 != chunkqueue_to_tempfiles(dest, errh)) 966 return -1; 967 dlen = 0; 968 iovcnt = 0; 969 } 970 971 if (__builtin_expect( (iovcnt < sizeof(iov)/sizeof(*iov)), 1)) { 972 for (c = src->first; c && c->type == MEM_CHUNK; c = c->next) { 973 off_t clen = chunk_remaining_length(c); 974 if (clen > len) clen = len; 975 iov[iovcnt].iov_base = c->mem->ptr + c->offset; 976 iov[iovcnt].iov_len = (size_t)clen; 977 len -= clen; 978 ++iovcnt; 979 if (0 == len) break; 980 if (__builtin_expect( (iovcnt == sizeof(iov)/sizeof(*iov)), 0)) 981 break; /*(not expecting large number of MEM_CHUNK)*/ 982 } 983 } 984 985 if (__builtin_expect( (0 == iovcnt), 0)) return 0; /*(should not happen)*/ 986 987 c = chunkqueue_get_append_tempfile(dest, errh); 988 if (NULL == c) 989 return -1; 990 #ifdef __COVERITY__ 991 if (c->file.fd < 0) return -1; 992 #endif 993 /* coverity[negative_returns : FALSE] */ 994 ssize_t wr = pwritev(c->file.fd, iov, (int)iovcnt, c->file.length); 995 996 /*(memory use in chunkqueues is expected to be limited before spilling 997 * to tempfiles, so common case will write entire iovec to tempfile, 998 * and we return amount written *from src cq*, even if partial write; 999 * (not looping here to retry writing more, but caller might loop))*/ 1000 1001 if (wr >= 0) { 1002 c->file.length += wr; 1003 dest->bytes_in += wr; 1004 if (dlen) { 1005 if (__builtin_expect( (wr < dlen), 0)) 1006 return 1007 chunkqueue_append_cqmem_to_tempfile_partial(dest,c,wr,errh); 1008 wr -= (ssize_t)dlen; 1009 dest->bytes_in -= dlen; /*(avoid double count in dest cq)*/ 1010 dest->bytes_out -= dlen; 1011 chunkqueue_mark_written(dest, dlen); 1012 } 1013 } 1014 else if (chunkqueue_append_tempfile_err(dest, errh, c)) 1015 wr = 0; /*(to trigger continue/retry in caller rather than error)*/ 1016 1017 return wr; 1018 } 1019 1020 #endif /* HAVE_PWRITEV */ 1021 1022 #ifdef HAVE_SPLICE 1023 1024 __attribute_cold__ 1025 __attribute_noinline__ 1026 static ssize_t chunkqueue_append_drain_pipe_tempfile(chunkqueue * const restrict cq, const int fd, unsigned int len, log_error_st * const restrict errh) { 1027 /* attempt to drain full 'len' from pipe 1028 * (even if len not reduced to opts->max_per_read limit) 1029 * since data may have already been moved from socket to pipe 1030 *(returns 0 on success, or -errno (negative errno) if error, 1031 * even if partial write occurred)*/ 1032 char buf[16384]; 1033 ssize_t rd; 1034 do { 1035 do { 1036 rd = read(fd, buf, sizeof(buf)); 1037 } while (rd < 0 && errno == EINTR); 1038 if (rd < 0) break; 1039 if (0 != chunkqueue_append_mem_to_tempfile(cq, buf, (size_t)rd, errh)) 1040 break; 1041 } while ((len -= (unsigned int)rd)); 1042 1043 if (0 == len) 1044 return 0; 1045 else { 1046 const int errnum = errno; 1047 if (cq->last && 0 == chunk_remaining_length(cq->last)) { 1048 /*(remove empty chunk and unlink tempfile)*/ 1049 chunkqueue_remove_empty_chunks(cq); 1050 } 1051 return -errnum; 1052 } 1053 } 1054 1055 ssize_t chunkqueue_append_splice_pipe_tempfile(chunkqueue * const restrict cq, const int fd, unsigned int len, log_error_st * const restrict errh) { 1056 /* check if prior MEM_CHUNK(s) exist and write to tempfile 1057 * (check first chunk only, since if we are using tempfiles, then 1058 * we expect further chunks to be tempfiles after starting tempfiles)*/ 1059 if (cq->first && cq->first->type == MEM_CHUNK) { 1060 int rc = chunkqueue_to_tempfiles(cq, errh); 1061 if (__builtin_expect( (0 != rc), 0)) return rc; 1062 } 1063 1064 /*(returns num bytes written, or -errno (negative errno) if error)*/ 1065 ssize_t total = 0; 1066 do { 1067 chunk * const c = chunkqueue_get_append_tempfile(cq, errh); 1068 if (__builtin_expect( (NULL == c), 0)) return -errno; 1069 1070 loff_t off = c->file.length; 1071 ssize_t wr = splice(fd, NULL, c->file.fd, &off, len, 1072 SPLICE_F_MOVE | SPLICE_F_NONBLOCK); 1073 1074 if (__builtin_expect(((size_t)wr == len), 1)) { 1075 c->file.length += len; 1076 cq->bytes_in += len; 1077 return total + len; 1078 } 1079 else if (wr >= 0) { 1080 /*(assume EINTR if partial write and retry; 1081 * retry might fail with ENOSPC if no more space on volume)*/ 1082 cq->bytes_in += wr; 1083 total += wr; 1084 len -= (size_t)wr; 1085 c->file.length += (size_t)wr; 1086 /* continue; retry */ 1087 } 1088 else { 1089 const int errnum = errno; 1090 switch (errnum) { 1091 case EAGAIN: 1092 #ifdef EWOULDBLOCK 1093 #if EWOULDBLOCK != EAGAIN 1094 case EWOULDBLOCK: 1095 #endif 1096 #endif 1097 if (0 == chunk_remaining_length(c)) { 1098 /*(remove empty chunk and unlink tempfile)*/ 1099 chunkqueue_remove_empty_chunks(cq); 1100 } 1101 return total; 1102 case EINVAL: /*(assume total == 0 if EINVAL)*/ 1103 wr = chunkqueue_append_drain_pipe_tempfile(cq, fd, len, errh); 1104 return (0 == wr) ? total + (ssize_t)len : wr; 1105 default: 1106 if (!chunkqueue_append_tempfile_err(cq, errh, c)) 1107 return -errnum; 1108 break; /* else continue; retry */ 1109 } 1110 } 1111 } while (len); 1112 return -EIO; /*(not reached)*/ 1113 } 1114 1115 static int cqpipes[2] = { -1, -1 }; 1116 1117 __attribute_cold__ 1118 __attribute_noinline__ 1119 void chunkqueue_internal_pipes(int init) { 1120 /*(intended for internal use within a single lighttpd process; 1121 * must be initialized after fork() and graceful-restart to avoid 1122 * sharing pipes between processes)*/ 1123 if (-1 != cqpipes[0]) { close(cqpipes[0]); cqpipes[0] = -1; } 1124 if (-1 != cqpipes[1]) { close(cqpipes[1]); cqpipes[1] = -1; } 1125 if (init) 1126 if (0 != fdevent_pipe_cloexec(cqpipes, 262144)) { } /*(ignore error)*/ 1127 } 1128 1129 __attribute_cold__ 1130 __attribute_noinline__ 1131 static void chunkqueue_pipe_read_discard (void) { 1132 char buf[16384]; 1133 ssize_t rd; 1134 do { 1135 rd = read(cqpipes[0], buf, sizeof(buf)); 1136 } while (rd > 0 || (rd < 0 && errno == EINTR)); 1137 if (rd < 0 1138 #ifdef EWOULDBLOCK 1139 #if EWOULDBLOCK != EAGAIN 1140 && errno != EWOULDBLOCK 1141 #endif 1142 #endif 1143 && errno != EAGAIN) { 1144 chunkqueue_internal_pipes(1); /*(close() and re-initialize)*/ 1145 } 1146 } 1147 1148 ssize_t chunkqueue_append_splice_sock_tempfile(chunkqueue * const restrict cq, const int fd, unsigned int len, log_error_st * const restrict errh) { 1149 /*(returns num bytes written, or -errno (negative errno) if error)*/ 1150 int * const pipes = cqpipes; 1151 if (-1 == pipes[1]) 1152 return -EINVAL; /*(not configured; not handled here)*/ 1153 1154 /* splice() socket data to intermediate pipe */ 1155 ssize_t wr = splice(fd, NULL, pipes[1], NULL, len, 1156 SPLICE_F_MOVE | SPLICE_F_NONBLOCK); 1157 if (__builtin_expect( (wr <= 0), 0)) 1158 return -EINVAL; /*(reuse to indicate not handled here)*/ 1159 len = (unsigned int)wr; 1160 1161 /* splice() data from intermediate pipe to tempfile */ 1162 wr = chunkqueue_append_splice_pipe_tempfile(cq, pipes[0], len, errh); 1163 if (wr < 0) /* expect (wr == (ssize_t)len) or (wr == -1) */ 1164 chunkqueue_pipe_read_discard();/* discard data from intermediate pipe */ 1165 return wr; 1166 } 1167 1168 #endif /* HAVE_SPLICE */ 1169 1170 int chunkqueue_steal_with_tempfiles(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len, log_error_st * const restrict errh) { 1171 /*(0-length first chunk (unexpected) is removed from src even if len == 0; 1172 * progress is made when caller loops on this func)*/ 1173 off_t clen; 1174 do { 1175 chunk * const c = src->first; 1176 if (__builtin_expect( (NULL == c), 0)) break; 1177 1178 #ifdef HAVE_PWRITEV 1179 1180 if (c->type == MEM_CHUNK) { 1181 clen = chunkqueue_append_cqmem_to_tempfile(dest, src, len, errh); 1182 if (__builtin_expect( (clen < 0), 0)) return -1; 1183 chunkqueue_mark_written(src, clen); 1184 } 1185 else { /* (c->type == FILE_CHUNK) */ 1186 clen = chunk_remaining_length(c); 1187 if (len < clen) clen = len; 1188 chunkqueue_steal(dest, src, clen); 1189 } 1190 1191 #else 1192 1193 clen = chunk_remaining_length(c); 1194 if (len < clen) clen = len; 1195 1196 switch (c->type) { 1197 case FILE_CHUNK: 1198 chunkqueue_steal(dest, src, clen); 1199 break; 1200 1201 case MEM_CHUNK: 1202 /* store bytes from memory chunk in tempfile */ 1203 if (clen 1204 && 0 != chunkqueue_append_mem_to_tempfile(dest, 1205 c->mem->ptr+c->offset, 1206 clen, errh)) 1207 return -1; 1208 chunkqueue_mark_written(src, clen); 1209 break; 1210 } 1211 1212 #endif 1213 } while ((len -= clen)); 1214 1215 return 0; 1216 } 1217 1218 void chunkqueue_append_cq_range (chunkqueue * const dst, const chunkqueue * const src, off_t offset, off_t len) { 1219 /* similar to chunkqueue_steal() but copy and append src range to dst cq */ 1220 /* (dst cq and src cq can be the same cq, so neither is marked restrict) */ 1221 1222 /* copy and append range len from src to dst */ 1223 for (const chunk *c = src->first; len > 0 && c != NULL; c = c->next) { 1224 /* scan into src to range offset (also skips empty chunks) */ 1225 off_t clen = chunk_remaining_length(c); 1226 if (offset >= clen) { 1227 offset -= clen; 1228 continue; 1229 } 1230 clen -= offset; 1231 if (len < clen) clen = len; 1232 len -= clen; 1233 1234 if (c->type == FILE_CHUNK) { 1235 chunkqueue_append_file(dst, c->mem, c->offset + offset, clen); 1236 chunkqueue_dup_file_chunk_fd(dst->last, c); 1237 } 1238 else { /*(c->type == MEM_CHUNK)*/ 1239 /*(string refs would reduce copying, 1240 * but this path is not expected to be hot)*/ 1241 chunkqueue_append_mem(dst, c->mem->ptr + c->offset + offset, clen); 1242 } 1243 offset = 0; 1244 } 1245 } 1246 1247 void chunkqueue_mark_written(chunkqueue *cq, off_t len) { 1248 cq->bytes_out += len; 1249 1250 for (chunk *c = cq->first; c; ) { 1251 off_t c_len = chunk_remaining_length(c); 1252 if (len >= c_len) { /* chunk got finished */ 1253 chunk * const x = c; 1254 c = c->next; 1255 len -= c_len; 1256 chunk_release(x); 1257 } 1258 else { /* partial chunk */ 1259 c->offset += len; 1260 cq->first = c; 1261 return; /* chunk not finished */ 1262 } 1263 } 1264 cq->first = cq->last = NULL; 1265 } 1266 1267 void chunkqueue_remove_finished_chunks(chunkqueue *cq) { 1268 for (chunk *c; (c = cq->first) && 0 == chunk_remaining_length(c); ){ 1269 if (NULL == (cq->first = c->next)) cq->last = NULL; 1270 chunk_release(c); 1271 } 1272 } 1273 1274 void chunkqueue_remove_empty_chunks(chunkqueue *cq) { 1275 chunk *c; 1276 chunkqueue_remove_finished_chunks(cq); 1277 1278 for (c = cq->first; c && c->next; c = c->next) { 1279 if (0 == chunk_remaining_length(c->next)) { 1280 chunk *empty = c->next; 1281 c->next = empty->next; 1282 if (empty == cq->last) cq->last = c; 1283 chunk_release(empty); 1284 } 1285 } 1286 } 1287 1288 void chunkqueue_compact_mem_offset(chunkqueue * const cq) { 1289 chunk * const restrict c = cq->first; 1290 if (0 == c->offset) return; 1291 if (c->type != MEM_CHUNK) return; /*(should not happen)*/ 1292 1293 buffer * const restrict b = c->mem; 1294 size_t len = buffer_clen(b) - c->offset; 1295 memmove(b->ptr, b->ptr+c->offset, len); 1296 c->offset = 0; 1297 buffer_truncate(b, len); 1298 } 1299 1300 void chunkqueue_compact_mem(chunkqueue *cq, size_t clen) { 1301 /* caller must guarantee that chunks in chunkqueue are MEM_CHUNK, 1302 * which is currently always true when reading input from client */ 1303 chunk *c = cq->first; 1304 buffer *b = c->mem; 1305 size_t len = buffer_clen(b) - c->offset; 1306 if (len >= clen) return; 1307 if (b->size > clen) { 1308 if (buffer_string_space(b) < clen - len) 1309 chunkqueue_compact_mem_offset(cq); 1310 } 1311 else { 1312 b = chunkqueue_prepend_buffer_open_sz(cq, clen+1); 1313 buffer_append_string_len(b, c->mem->ptr + c->offset, len); 1314 cq->first->next = c->next; 1315 if (NULL == c->next) cq->last = cq->first; 1316 chunk_release(c); 1317 c = cq->first; 1318 } 1319 1320 for (chunk *fc = c; ((clen -= len) && (c = fc->next)); ) { 1321 len = buffer_clen(c->mem) - c->offset; 1322 if (len > clen) { 1323 buffer_append_string_len(b, c->mem->ptr + c->offset, clen); 1324 c->offset += clen; 1325 break; 1326 } 1327 buffer_append_string_len(b, c->mem->ptr + c->offset, len); 1328 fc->next = c->next; 1329 if (NULL == c->next) cq->last = fc; 1330 chunk_release(c); 1331 } 1332 /* chunkqueue_prepend_buffer_commit() is not called here; 1333 * no data added/removed from chunkqueue; consolidated only */ 1334 } 1335 1336 static int chunk_open_file_chunk(chunk * const restrict c, log_error_st * const restrict errh) { 1337 if (-1 == c->file.fd) { 1338 /* (permit symlinks; should already have been checked. However, TOC-TOU remains) */ 1339 if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, 1, O_RDONLY, 0))) { 1340 log_perror(errh, __FILE__, __LINE__, "open failed: %s",c->mem->ptr); 1341 return -1; 1342 } 1343 } 1344 1345 /*(skip file size checks if file is temp file created by lighttpd)*/ 1346 if (c->file.is_temp) return 0; 1347 1348 struct stat st; 1349 if (-1 == fstat(c->file.fd, &st)) { 1350 log_perror(errh, __FILE__, __LINE__, "fstat failed"); 1351 return -1; 1352 } 1353 1354 const off_t offset = c->offset; 1355 const off_t len = c->file.length - c->offset; 1356 force_assert(offset >= 0 && len >= 0); 1357 if (offset > st.st_size - len) { 1358 log_error(errh, __FILE__, __LINE__, "file shrunk: %s", c->mem->ptr); 1359 return -1; 1360 } 1361 1362 return 0; 1363 } 1364 1365 int chunkqueue_open_file_chunk(chunkqueue * const restrict cq, log_error_st * const restrict errh) { 1366 return chunk_open_file_chunk(cq->first, errh); 1367 } 1368 1369 1370 static ssize_t 1371 chunkqueue_write_data (const int fd, const void *buf, size_t len) 1372 { 1373 ssize_t wr = 0; 1374 if (len) 1375 do { wr = write(fd, buf, len); } while (-1 == wr && errno == EINTR); 1376 return wr; 1377 } 1378 1379 1380 #ifdef HAVE_MMAP 1381 __attribute_cold__ 1382 #endif 1383 __attribute_noinline__ 1384 static ssize_t 1385 chunkqueue_write_chunk_file_intermed (const int fd, chunk * const restrict c, log_error_st * const errh) 1386 { 1387 char buf[16384]; 1388 char *data = buf; 1389 const off_t len = c->file.length - c->offset; 1390 uint32_t dlen = len < (off_t)sizeof(buf) ? (uint32_t)len : sizeof(buf); 1391 chunkqueue cq = {c,c,0,0,0,0,0}; /*(fake cq for chunkqueue_peek_data())*/ 1392 if (0 != chunkqueue_peek_data(&cq, &data, &dlen, errh) && 0 == dlen) 1393 return -1; 1394 return chunkqueue_write_data(fd, data, dlen); 1395 } 1396 1397 1398 #if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE \ 1399 && (!defined _LARGEFILE_SOURCE || defined HAVE_SENDFILE64) \ 1400 && defined(__linux__) && !defined HAVE_SENDFILE_BROKEN 1401 #include <sys/sendfile.h> 1402 #include <stdint.h> 1403 #endif 1404 static ssize_t 1405 chunkqueue_write_chunk_file (const int fd, chunk * const restrict c, log_error_st * const errh) 1406 { 1407 /*(similar to network_write_file_chunk_mmap(), but does not use send() on 1408 * Windows because fd is expected to be file or pipe here, not socket)*/ 1409 1410 if (0 != chunk_open_file_chunk(c, errh)) 1411 return -1; 1412 1413 const off_t len = c->file.length - c->offset; 1414 if (0 == len) return 0; /*(sanity check)*/ 1415 1416 #if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE \ 1417 && (!defined _LARGEFILE_SOURCE || defined HAVE_SENDFILE64) \ 1418 && defined(__linux__) && !defined HAVE_SENDFILE_BROKEN 1419 /* Linux kernel >= 2.6.33 supports sendfile() between most fd types */ 1420 off_t offset = c->offset; 1421 const ssize_t wr = 1422 sendfile(fd, c->file.fd, &offset, len < INT32_MAX ? len : INT32_MAX); 1423 if (__builtin_expect( (wr >= 0), 1) || (errno != EINVAL && errno != ENOSYS)) 1424 return wr; 1425 /*(could fallback to mmap, but if sendfile fails on linux, mmap may, too)*/ 1426 #elif defined(HAVE_MMAP) 1427 /*(chunkqueue_write_chunk() caller must protect against SIGBUS, if needed)*/ 1428 const chunk_file_view * const restrict cfv = 1429 chunkqueue_chunk_file_view(c, len, errh); 1430 if (NULL != cfv) { 1431 const off_t mmap_avail = chunk_file_view_dlen(cfv, c->offset); 1432 return chunkqueue_write_data(fd, chunk_file_view_dptr(cfv, c->offset), 1433 len <= mmap_avail ? len : mmap_avail); 1434 } 1435 #endif 1436 1437 return chunkqueue_write_chunk_file_intermed(fd, c, errh); 1438 } 1439 1440 1441 static ssize_t 1442 chunkqueue_write_chunk_mem (const int fd, const chunk * const restrict c) 1443 { 1444 const void * const buf = c->mem->ptr + c->offset; 1445 const size_t len = buffer_clen(c->mem) - (size_t)c->offset; 1446 return chunkqueue_write_data(fd, buf, len); 1447 } 1448 1449 1450 ssize_t 1451 chunkqueue_write_chunk (const int fd, chunkqueue * const restrict cq, log_error_st * const restrict errh) 1452 { 1453 /*(note: expects non-empty cq->first)*/ 1454 chunk * const c = cq->first; 1455 switch (c->type) { 1456 case MEM_CHUNK: 1457 return chunkqueue_write_chunk_mem(fd, c); 1458 case FILE_CHUNK: 1459 return chunkqueue_write_chunk_file(fd, c, errh); 1460 default: 1461 errno = EINVAL; 1462 return -1; 1463 } 1464 } 1465 1466 1467 ssize_t 1468 chunkqueue_write_chunk_to_pipe (const int fd, chunkqueue * const restrict cq, log_error_st * const restrict errh) 1469 { 1470 #ifdef HAVE_SPLICE /* splice() temp files to pipe on Linux */ 1471 chunk * const c = cq->first; 1472 if (c->type == FILE_CHUNK) { 1473 const size_t len = (size_t)(c->file.length - c->offset); 1474 loff_t abs_offset = c->offset; 1475 if (__builtin_expect( (0 == len), 0)) return 0; 1476 return (0 == chunk_open_file_chunk(c, errh)) 1477 ? splice(c->file.fd, &abs_offset, fd, NULL, len, SPLICE_F_NONBLOCK) 1478 : -1; 1479 } 1480 #endif 1481 return chunkqueue_write_chunk(fd, cq, errh); 1482 } 1483 1484 1485 void 1486 chunkqueue_small_resp_optim (chunkqueue * const restrict cq) 1487 { 1488 /*(caller must verify response is small (and non-empty) before calling)*/ 1489 /*(caller must verify first chunk is MEM_CHUNK, i.e. response headers)*/ 1490 /*(caller must verify response is non-zero length)*/ 1491 1492 /*(optimization to use fewer syscalls to send a small response by reading 1493 * small files into memory, thereby avoiding use of sendfile() and multiple 1494 * calls to writev() (benefit for cleartext (non-TLS) and <= HTTP/1.1)) 1495 *(If TLS, then will shortly need to be in memory for encryption anyway)*/ 1496 1497 /*assert(cq->first);*/ 1498 /*assert(cq->first->type == MEM_CHUNK);*/ 1499 /*assert(cq->first->next);*/ 1500 chunk * restrict c = cq->first; 1501 chunk * const restrict filec = c->next; /*(require file already be open)*/ 1502 if (filec != cq->last || filec->type != FILE_CHUNK || filec->file.fd < 0) 1503 return; 1504 1505 /* Note: there should be no size change in chunkqueue, 1506 * so cq->bytes_in and cq->bytes_out should not be modified */ 1507 1508 off_t len = filec->file.length - filec->offset; 1509 if ((size_t)len > buffer_string_space(c->mem)) { 1510 c->next = chunk_acquire((size_t)len+1); 1511 c = c->next; 1512 /*c->next = filec;*/ 1513 } 1514 /* detach filec from chunkqueue; file expected to be read fully */ 1515 c->next = NULL; 1516 cq->last = c; 1517 1518 ssize_t rd; 1519 off_t offset = 0; 1520 char * const ptr = buffer_extend(c->mem, len); 1521 do { 1522 rd = chunk_file_pread(filec->file.fd, ptr+offset, (size_t)len, 1523 filec->offset+offset); 1524 } while (rd > 0 && (offset += rd, len -= rd)); 1525 /*(contents of chunkqueue kept valid even if error reading from file)*/ 1526 if (__builtin_expect( (0 == len), 1)) 1527 chunk_release(filec); 1528 else { /*(unexpected; error recovery)*/ 1529 buffer_truncate(c->mem, (uint32_t)(ptr + offset - c->mem->ptr)); 1530 cq->last = c->next = filec; 1531 if (offset) 1532 filec->offset += offset; 1533 else if (__builtin_expect( (cq->first != c), 0)) { 1534 cq->first->next = filec; 1535 chunk_release(c); 1536 } 1537 } 1538 } 1539 1540 1541 #if 0 1542 #ifdef HAVE_MMAP 1543 __attribute_noinline__ 1544 static off_t 1545 chunk_setjmp_memcpy_cb (void *dst, const void *src, off_t len) 1546 { 1547 /*(on 32-bit systems, caller should assert len <= SIZE_MAX)*/ 1548 memcpy(dst, src, (size_t)len); 1549 return len; 1550 } 1551 #endif 1552 #endif 1553 1554 1555 int 1556 chunkqueue_peek_data (chunkqueue * const cq, 1557 char ** const data, uint32_t * const dlen, 1558 log_error_st * const errh) 1559 { 1560 char * const data_in = *data; 1561 const uint32_t data_insz = *dlen; 1562 *dlen = 0; 1563 1564 for (chunk *c = cq->first; c; ) { 1565 uint32_t space = data_insz - *dlen; 1566 switch (c->type) { 1567 case MEM_CHUNK: 1568 { 1569 uint32_t have = buffer_clen(c->mem) - (uint32_t)c->offset; 1570 if (have > space) 1571 have = space; 1572 if (__builtin_expect( (0 == have), 0)) 1573 break; 1574 if (*dlen) 1575 memcpy(data_in + *dlen, c->mem->ptr + c->offset, have); 1576 else 1577 *data = c->mem->ptr + c->offset; /*(reference; defer copy)*/ 1578 *dlen += have; 1579 break; 1580 } 1581 1582 case FILE_CHUNK: 1583 if (c->file.fd >= 0 || 0 == chunk_open_file_chunk(c, errh)) { 1584 off_t len = c->file.length - c->offset; 1585 if (len > (off_t)space) 1586 len = (off_t)space; 1587 if (__builtin_expect( (0 == len), 0)) 1588 break; 1589 1590 #if 0 /* XXX: might improve performance on some system workloads */ 1591 #ifdef HAVE_MMAP 1592 /* mmap file to access data 1593 * fd need not be kept open for the mmap once 1594 * the mmap has been created, but is currently kept open for 1595 * other pre-existing logic which checks fd and opens file, 1596 * such as the condition for entering this code block above. */ 1597 /* Note: current use is with somewhat large buffers, e.g. 128k. 1598 * If larger buffers are used, then upper limit, e.g. 512k, 1599 * should be set for 32-bit to avoid address space issues) */ 1600 /* Note: under heavy load (or microbenchmark), system-reported 1601 * memory use for RSS can be very, very large, due to presence 1602 * of lots and lots of temp file read-only memory maps. 1603 * pmap -X and exclude lighttpd mmap files to get a better 1604 * view of memory use */ 1605 const chunk_file_view * const restrict cfv = (!c->file.is_temp) 1606 ? chunkqueue_chunk_file_view(c, len, errh) 1607 : NULL; 1608 if (cfv && chunk_file_view_dlen(cfv, c->offset) >= len) { 1609 /*(check (above) that mapped chunk length >= requested len)*/ 1610 char * const mdata = chunk_file_view_dptr(cfv, c->offset); 1611 if (!c->file.is_temp) {/*(might be changed to policy flag)*/ 1612 if (sys_setjmp_eval3(chunk_setjmp_memcpy_cb, 1613 data_in+*dlen, mdata, len) < 0) { 1614 log_error(errh, __FILE__, __LINE__, 1615 "SIGBUS in mmap: %s %d", c->mem->ptr, c->file.fd); 1616 return -1; 1617 } 1618 } 1619 else if (*dlen) 1620 memcpy(data_in+*dlen, mdata, (size_t)len); 1621 else 1622 *data = mdata; 1623 *dlen += (uint32_t)len; 1624 break; 1625 } 1626 #endif 1627 #endif 1628 1629 ssize_t rd = chunk_file_pread(c->file.fd, data_in+*dlen, 1630 (size_t)len, c->offset); 1631 if (rd <= 0) { /* -1 error; 0 EOF (unexpected) */ 1632 log_perror(errh, __FILE__, __LINE__, "read(\"%s\")", 1633 c->mem->ptr); 1634 return -1; 1635 } 1636 1637 *dlen += (uint32_t)rd; 1638 break; 1639 } 1640 return -1; 1641 1642 default: 1643 return -1; 1644 } 1645 1646 if (*dlen == data_insz) 1647 break; 1648 1649 c = c->next; 1650 if (NULL == c) 1651 break; 1652 1653 if (*dlen && *data != data_in) { 1654 memcpy(data_in, *data, *dlen); 1655 *data = data_in; 1656 } 1657 } 1658 1659 return 0; 1660 } 1661 1662 1663 int 1664 chunkqueue_read_data (chunkqueue * const cq, 1665 char * const data, const uint32_t dlen, 1666 log_error_st * const errh) 1667 { 1668 char *ptr = data; 1669 uint32_t len = dlen; 1670 if (chunkqueue_peek_data(cq, &ptr, &len, errh) < 0 || len != dlen) 1671 return -1; 1672 if (data != ptr) memcpy(data, ptr, len); 1673 chunkqueue_mark_written(cq, len); 1674 return 0; 1675 } 1676 1677 1678 buffer * 1679 chunkqueue_read_squash (chunkqueue * const restrict cq, log_error_st * const restrict errh) 1680 { 1681 /* read and replace chunkqueue contents with single MEM_CHUNK. 1682 * cq->bytes_out is not modified */ 1683 1684 off_t cqlen = chunkqueue_length(cq); 1685 if (cqlen >= UINT32_MAX) return NULL; 1686 1687 if (cq->first && NULL == cq->first->next && cq->first->type == MEM_CHUNK) 1688 return cq->first->mem; 1689 1690 chunk * const c = chunk_acquire((uint32_t)cqlen+1); 1691 char *data = c->mem->ptr; 1692 uint32_t dlen = (uint32_t)cqlen; 1693 int rc = chunkqueue_peek_data(cq, &data, &dlen, errh); 1694 if (rc < 0) { 1695 chunk_release(c); 1696 return NULL; 1697 } 1698 buffer_truncate(c->mem, dlen); 1699 1700 chunkqueue_release_chunks(cq); 1701 chunkqueue_append_chunk(cq, c); 1702 return c->mem; 1703 } 1704 1705 1706 #ifdef HAVE_MMAP 1707 1708 const chunk_file_view * 1709 chunkqueue_chunk_file_viewadj (chunk * const c, off_t n, log_error_st * restrict errh) 1710 { 1711 /*assert(c->type == FILE_CHUNK);*/ 1712 chunk_file_view * restrict cfv = c->file.view; 1713 1714 if (NULL == cfv) { 1715 /* XXX: might add global config check to enable/disable mmap use here */ 1716 cfv = c->file.view = chunk_file_view_init(); 1717 } 1718 else if (MAP_FAILED != cfv->mptr) 1719 munmap(cfv->mptr, (size_t)cfv->mlen); 1720 /*cfv->mptr= MAP_FAILED;*//*(assigned below)*/ 1721 1722 if (c->file.fd < 0 && 0 != chunk_open_file_chunk(c, errh)) { 1723 c->file.view = chunk_file_view_failed(cfv); 1724 return NULL; 1725 } 1726 1727 cfv->foff = mmap_align_offset(c->offset); 1728 1729 if (0 != n) { 1730 cfv->mlen = c->offset - cfv->foff + n; 1731 #if !(defined(_LP64) || defined(__LP64__) || defined(_WIN64)) 1732 /*(consider 512k blocks if this func is used more generically)*/ 1733 const off_t mmap_chunk_size = 8 * 1024 * 1024; 1734 if (cfv->mlen > mmap_chunk_size) 1735 cfv->mlen = mmap_chunk_size; 1736 #endif 1737 } 1738 else 1739 cfv->mlen = MMAP_CHUNK_SIZE; 1740 /* XXX: 64-bit might consider larger min block size, or even entire file */ 1741 if (cfv->mlen < MMAP_CHUNK_SIZE) 1742 cfv->mlen = MMAP_CHUNK_SIZE; 1743 if (cfv->mlen > c->file.length - cfv->foff) 1744 cfv->mlen = c->file.length - cfv->foff; 1745 1746 cfv->mptr = mmap(NULL, (size_t)cfv->mlen, PROT_READ, 1747 c->file.is_temp ? MAP_PRIVATE : chunk_mmap_flags, 1748 c->file.fd, cfv->foff); 1749 1750 if (__builtin_expect( (MAP_FAILED == cfv->mptr), 0)) { 1751 if (__builtin_expect( (errno == EINVAL), 0)) { 1752 chunk_mmap_flags &= ~MAP_SHARED; 1753 chunk_mmap_flags |= MAP_PRIVATE; 1754 cfv->mptr = mmap(NULL, (size_t)cfv->mlen, PROT_READ, 1755 MAP_PRIVATE, c->file.fd, cfv->foff); 1756 } 1757 if (__builtin_expect( (MAP_FAILED == cfv->mptr), 0)) { 1758 c->file.view = chunk_file_view_failed(cfv); 1759 return NULL; 1760 } 1761 } 1762 1763 #if 0 /*(review callers before changing; some expect open file)*/ 1764 /* close() fd as soon as fully mmap() rather than when done w/ chunk 1765 * (possibly worthwhile to keep active fd count lower) 1766 * (probably only reasonable if entire file is mapped) */ 1767 if (c->file.is_temp && !c->file.refchg) { 1768 close(c->file.fd); 1769 c->file.fd = -1; 1770 } 1771 #endif 1772 1773 #if 0 1774 /* disable madvise unless we find common cases where there is a benefit 1775 * (??? madvise for full mmap length or only for original requested n ???) 1776 * (??? might additional flags param to func to indicate madvise pref ???) 1777 * (??? might experiment with Linux mmap flags MAP_POPULATE|MAP_PRIVATE) 1778 * (??? might experiment with madvise MADV_POPULATE_READ (since Linux 5.14)) 1779 * note: caller might be in better position to know if starting an mmap 1780 * which will be flushed in entirety, and perform madvise at that point, 1781 * perhaps with MADV_SEQUENTIAL */ 1782 #ifdef HAVE_MADVISE 1783 if (cfv->mlen > 65536) /*(skip syscall if size <= 64KB)*/ 1784 (void)madvise(cfv->mptr, (size_t)cfv->mlen, MADV_WILLNEED); 1785 #endif 1786 #endif 1787 1788 return cfv; 1789 } 1790 1791 #endif /* HAVE_MMAP */ 1792