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 17 #include <stdlib.h> 18 #include <fcntl.h> 19 #include <unistd.h> 20 21 #include <errno.h> 22 #include <string.h> 23 24 /* default 1MB, upper limit 128MB */ 25 #define DEFAULT_TEMPFILE_SIZE (1 * 1024 * 1024) 26 #define MAX_TEMPFILE_SIZE (128 * 1024 * 1024) 27 28 static size_t chunk_buf_sz = 8192; 29 static chunk *chunks, *chunks_oversized; 30 static chunk *chunk_buffers; 31 static const array *chunkqueue_default_tempdirs = NULL; 32 static off_t chunkqueue_default_tempfile_size = DEFAULT_TEMPFILE_SIZE; 33 34 void chunkqueue_set_chunk_size (size_t sz) 35 { 36 chunk_buf_sz = sz > 0 ? ((sz + 1023) & ~1023uL) : 8192; 37 } 38 39 void chunkqueue_set_tempdirs_default_reset (void) 40 { 41 chunkqueue_default_tempdirs = NULL; 42 chunkqueue_default_tempfile_size = DEFAULT_TEMPFILE_SIZE; 43 } 44 45 /* chunk buffer (c->mem) is never NULL; specialize routines from buffer.h */ 46 __attribute_pure__ 47 static inline size_t chunk_buffer_string_length(const buffer *b) { 48 return 0 != b->used ? b->used - 1 : 0; 49 } 50 __attribute_pure__ 51 static inline int chunk_buffer_string_is_empty(const buffer *b) { 52 return b->used < 2; 53 } 54 __attribute_pure__ 55 static inline size_t chunk_buffer_string_space(const buffer *b) { 56 return b->size ? b->size - (b->used | (0 == b->used)) : 0; 57 } 58 59 chunkqueue *chunkqueue_init(void) { 60 chunkqueue *cq; 61 62 cq = calloc(1, sizeof(*cq)); 63 force_assert(NULL != cq); 64 65 cq->first = NULL; 66 cq->last = NULL; 67 68 cq->tempdirs = chunkqueue_default_tempdirs; 69 cq->upload_temp_file_size = chunkqueue_default_tempfile_size; 70 71 return cq; 72 } 73 74 __attribute_returns_nonnull__ 75 static chunk *chunk_init(size_t sz) { 76 chunk *c; 77 78 c = calloc(1, sizeof(*c)); 79 force_assert(NULL != c); 80 81 c->type = MEM_CHUNK; 82 c->mem = buffer_init(); 83 c->file.start = c->file.length = c->file.mmap.offset = 0; 84 c->file.fd = -1; 85 c->file.mmap.start = MAP_FAILED; 86 c->file.mmap.length = 0; 87 c->file.is_temp = 0; 88 c->offset = 0; 89 c->next = NULL; 90 91 buffer_string_prepare_copy(c->mem, sz-1); 92 93 return c; 94 } 95 96 static void chunk_reset_file_chunk(chunk *c) { 97 if (c->file.is_temp && !chunk_buffer_string_is_empty(c->mem)) { 98 unlink(c->mem->ptr); 99 } 100 if (c->file.fd != -1) { 101 close(c->file.fd); 102 c->file.fd = -1; 103 } 104 if (MAP_FAILED != c->file.mmap.start) { 105 munmap(c->file.mmap.start, c->file.mmap.length); 106 c->file.mmap.start = MAP_FAILED; 107 } 108 c->file.start = c->file.length = c->file.mmap.offset = 0; 109 c->file.mmap.length = 0; 110 c->file.is_temp = 0; 111 c->type = MEM_CHUNK; 112 } 113 114 static void chunk_reset(chunk *c) { 115 if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c); 116 117 buffer_clear(c->mem); 118 c->offset = 0; 119 } 120 121 static void chunk_free(chunk *c) { 122 if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c); 123 buffer_free(c->mem); 124 free(c); 125 } 126 127 buffer * chunk_buffer_acquire(void) { 128 chunk *c; 129 buffer *b; 130 if (chunks) { 131 c = chunks; 132 chunks = c->next; 133 } 134 else { 135 c = chunk_init(chunk_buf_sz); 136 } 137 c->next = chunk_buffers; 138 chunk_buffers = c; 139 b = c->mem; 140 c->mem = NULL; 141 return b; 142 } 143 144 void chunk_buffer_release(buffer *b) { 145 if (NULL == b) return; 146 if (b->size >= chunk_buf_sz && chunk_buffers) { 147 chunk *c = chunk_buffers; 148 chunk_buffers = c->next; 149 c->mem = b; 150 c->next = chunks; 151 chunks = c; 152 buffer_clear(b); 153 } 154 else { 155 buffer_free(b); 156 } 157 } 158 159 __attribute_returns_nonnull__ 160 static chunk * chunk_acquire(size_t sz) { 161 if (sz <= chunk_buf_sz) { 162 if (chunks) { 163 chunk *c = chunks; 164 chunks = c->next; 165 return c; 166 } 167 sz = chunk_buf_sz; 168 } 169 else { 170 sz = (sz + 8191) & ~8191uL; 171 /* future: might have buckets of certain sizes, up to socket buf sizes*/ 172 if (chunks_oversized && chunks_oversized->mem->size >= sz) { 173 chunk *c = chunks_oversized; 174 chunks_oversized = c->next; 175 return c; 176 } 177 } 178 179 return chunk_init(sz); 180 } 181 182 static void chunk_release(chunk *c) { 183 const size_t sz = c->mem->size; 184 if (sz == chunk_buf_sz) { 185 chunk_reset(c); 186 c->next = chunks; 187 chunks = c; 188 } 189 else if (sz > chunk_buf_sz) { 190 chunk_reset(c); 191 chunk **co = &chunks_oversized; 192 while (*co && sz < (*co)->mem->size) co = &(*co)->next; 193 c->next = *co; 194 *co = c; 195 } 196 else { 197 chunk_free(c); 198 } 199 } 200 201 void chunkqueue_chunk_pool_clear(void) 202 { 203 for (chunk *next, *c = chunks; c; c = next) { 204 next = c->next; 205 chunk_free(c); 206 } 207 chunks = NULL; 208 for (chunk *next, *c = chunks_oversized; c; c = next) { 209 next = c->next; 210 chunk_free(c); 211 } 212 chunks_oversized = NULL; 213 } 214 215 void chunkqueue_chunk_pool_free(void) 216 { 217 chunkqueue_chunk_pool_clear(); 218 for (chunk *next, *c = chunk_buffers; c; c = next) { 219 next = c->next; 220 c->mem = buffer_init(); /*(chunk_reset() expects c->mem != NULL)*/ 221 chunk_free(c); 222 } 223 chunk_buffers = NULL; 224 } 225 226 __attribute_pure__ 227 static off_t chunk_remaining_length(const chunk *c) { 228 /* MEM_CHUNK or FILE_CHUNK */ 229 return (c->type == MEM_CHUNK 230 ? (off_t)chunk_buffer_string_length(c->mem) 231 : c->file.length) 232 - c->offset; 233 } 234 235 static void chunkqueue_release_chunks(chunkqueue *cq) { 236 cq->last = NULL; 237 for (chunk *c; (c = cq->first); ) { 238 cq->first = c->next; 239 chunk_release(c); 240 } 241 } 242 243 void chunkqueue_free(chunkqueue *cq) { 244 if (NULL == cq) return; 245 chunkqueue_release_chunks(cq); 246 free(cq); 247 } 248 249 static void chunkqueue_prepend_chunk(chunkqueue * const restrict cq, chunk * const restrict c) { 250 if (NULL == (c->next = cq->first)) cq->last = c; 251 cq->first = c; 252 } 253 254 static void chunkqueue_append_chunk(chunkqueue * const restrict cq, chunk * const restrict c) { 255 c->next = NULL; 256 *(cq->last ? &cq->last->next : &cq->first) = c; 257 cq->last = c; 258 } 259 260 __attribute_returns_nonnull__ 261 static chunk * chunkqueue_prepend_mem_chunk(chunkqueue *cq, size_t sz) { 262 chunk *c = chunk_acquire(sz); 263 chunkqueue_prepend_chunk(cq, c); 264 return c; 265 } 266 267 __attribute_returns_nonnull__ 268 static chunk * chunkqueue_append_mem_chunk(chunkqueue *cq, size_t sz) { 269 chunk *c = chunk_acquire(sz); 270 chunkqueue_append_chunk(cq, c); 271 return c; 272 } 273 274 __attribute_returns_nonnull__ 275 static chunk * chunkqueue_append_file_chunk(chunkqueue * const restrict cq, const buffer * const restrict fn, off_t offset, off_t len) { 276 chunk *c = chunk_acquire(buffer_string_length(fn)+1); 277 chunkqueue_append_chunk(cq, c); 278 c->type = FILE_CHUNK; 279 c->file.start = offset; 280 c->file.length = len; 281 cq->bytes_in += len; 282 buffer_copy_buffer(c->mem, fn); 283 return c; 284 } 285 286 void chunkqueue_reset(chunkqueue *cq) { 287 chunkqueue_release_chunks(cq); 288 cq->bytes_in = 0; 289 cq->bytes_out = 0; 290 cq->tempdir_idx = 0; 291 } 292 293 void chunkqueue_append_file_fd(chunkqueue * const restrict cq, const buffer * const restrict fn, int fd, off_t offset, off_t len) { 294 if (len > 0) { 295 (chunkqueue_append_file_chunk(cq, fn, offset, len))->file.fd = fd; 296 } 297 else { 298 close(fd); 299 } 300 } 301 302 void chunkqueue_append_file(chunkqueue * const restrict cq, const buffer * const restrict fn, off_t offset, off_t len) { 303 if (len > 0) { 304 chunkqueue_append_file_chunk(cq, fn, offset, len); 305 } 306 } 307 308 309 static int chunkqueue_append_mem_extend_chunk(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) { 310 chunk *c = cq->last; 311 if (0 == len) return 1; 312 if (c != NULL && c->type == MEM_CHUNK 313 && chunk_buffer_string_space(c->mem) >= len) { 314 buffer_append_string_len(c->mem, mem, len); 315 cq->bytes_in += len; 316 return 1; 317 } 318 return 0; 319 } 320 321 322 void chunkqueue_append_buffer(chunkqueue * const restrict cq, buffer * const restrict mem) { 323 chunk *c; 324 size_t len = buffer_string_length(mem); 325 if (len < 256 && chunkqueue_append_mem_extend_chunk(cq, mem->ptr, len)) return; 326 327 c = chunkqueue_append_mem_chunk(cq, chunk_buf_sz); 328 cq->bytes_in += len; 329 buffer_move(c->mem, mem); 330 } 331 332 333 void chunkqueue_append_mem(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) { 334 chunk *c; 335 if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len)) 336 return; 337 338 c = chunkqueue_append_mem_chunk(cq, len+1); 339 cq->bytes_in += len; 340 buffer_copy_string_len(c->mem, mem, len); 341 } 342 343 344 void chunkqueue_append_mem_min(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) { 345 chunk *c; 346 if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len)) 347 return; 348 349 c = chunk_init(len+1); 350 chunkqueue_append_chunk(cq, c); 351 cq->bytes_in += len; 352 buffer_copy_string_len(c->mem, mem, len); 353 } 354 355 356 void chunkqueue_append_chunkqueue(chunkqueue * const restrict cq, chunkqueue * const restrict src) { 357 if (src == NULL || NULL == src->first) return; 358 359 if (NULL == cq->first) { 360 cq->first = src->first; 361 } else { 362 cq->last->next = src->first; 363 } 364 cq->last = src->last; 365 cq->bytes_in += (src->bytes_in - src->bytes_out); 366 367 src->first = NULL; 368 src->last = NULL; 369 src->bytes_out = src->bytes_in; 370 } 371 372 373 buffer * chunkqueue_prepend_buffer_open_sz(chunkqueue *cq, size_t sz) { 374 chunk * const c = chunkqueue_prepend_mem_chunk(cq, sz); 375 return c->mem; 376 } 377 378 379 buffer * chunkqueue_prepend_buffer_open(chunkqueue *cq) { 380 return chunkqueue_prepend_buffer_open_sz(cq, chunk_buf_sz); 381 } 382 383 384 void chunkqueue_prepend_buffer_commit(chunkqueue *cq) { 385 cq->bytes_in += chunk_buffer_string_length(cq->first->mem); 386 } 387 388 389 buffer * chunkqueue_append_buffer_open_sz(chunkqueue *cq, size_t sz) { 390 chunk * const c = chunkqueue_append_mem_chunk(cq, sz); 391 return c->mem; 392 } 393 394 395 buffer * chunkqueue_append_buffer_open(chunkqueue *cq) { 396 return chunkqueue_append_buffer_open_sz(cq, chunk_buf_sz); 397 } 398 399 400 void chunkqueue_append_buffer_commit(chunkqueue *cq) { 401 cq->bytes_in += chunk_buffer_string_length(cq->last->mem); 402 } 403 404 405 static void chunkqueue_remove_empty_chunks(chunkqueue *cq); 406 407 408 char * chunkqueue_get_memory(chunkqueue * const restrict cq, size_t * const restrict len) { 409 size_t sz = *len ? *len : (chunk_buf_sz >> 1); 410 buffer *b; 411 chunk *c = cq->last; 412 if (NULL != c && MEM_CHUNK == c->type) { 413 /* return pointer into existing buffer if large enough */ 414 size_t avail = chunk_buffer_string_space(c->mem); 415 if (avail >= sz) { 416 *len = avail; 417 b = c->mem; 418 return b->ptr + chunk_buffer_string_length(b); 419 } 420 } 421 422 /* allocate new chunk */ 423 b = chunkqueue_append_buffer_open_sz(cq, sz); 424 *len = chunk_buffer_string_space(b); 425 return b->ptr; 426 } 427 428 void chunkqueue_use_memory(chunkqueue * const restrict cq, chunk *ckpt, size_t len) { 429 buffer *b = cq->last->mem; 430 431 if (len > 0) { 432 buffer_commit(b, len); 433 cq->bytes_in += len; 434 if (cq->last == ckpt || NULL == ckpt || MEM_CHUNK != ckpt->type 435 || len > chunk_buffer_string_space(ckpt->mem)) return; 436 437 buffer_append_string_buffer(ckpt->mem, b); 438 } 439 else if (!chunk_buffer_string_is_empty(b)) { /*(cq->last == ckpt)*/ 440 return; /* last chunk is not empty */ 441 } 442 443 /* remove empty last chunk */ 444 chunk_release(cq->last); 445 cq->last = ckpt; 446 *(ckpt ? &ckpt->next : &cq->first) = NULL; 447 } 448 449 void chunkqueue_set_tempdirs_default (const array *tempdirs, off_t upload_temp_file_size) { 450 chunkqueue_default_tempdirs = tempdirs; 451 chunkqueue_default_tempfile_size 452 = (0 == upload_temp_file_size) ? DEFAULT_TEMPFILE_SIZE 453 : (upload_temp_file_size > MAX_TEMPFILE_SIZE) ? MAX_TEMPFILE_SIZE 454 : upload_temp_file_size; 455 } 456 457 void chunkqueue_set_tempdirs(chunkqueue * const restrict cq, const array * const restrict tempdirs, off_t upload_temp_file_size) { 458 force_assert(NULL != cq); 459 cq->tempdirs = tempdirs; 460 cq->upload_temp_file_size 461 = (0 == upload_temp_file_size) ? DEFAULT_TEMPFILE_SIZE 462 : (upload_temp_file_size > MAX_TEMPFILE_SIZE) ? MAX_TEMPFILE_SIZE 463 : upload_temp_file_size; 464 cq->tempdir_idx = 0; 465 } 466 467 void chunkqueue_steal(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len) { 468 while (len > 0) { 469 chunk *c = src->first; 470 off_t clen = 0, use; 471 472 if (NULL == c) break; 473 474 clen = chunk_remaining_length(c); 475 if (0 == clen) { 476 /* drop empty chunk */ 477 src->first = c->next; 478 if (c == src->last) src->last = NULL; 479 chunk_release(c); 480 continue; 481 } 482 483 use = len >= clen ? clen : len; 484 len -= use; 485 486 if (use == clen) { 487 /* move complete chunk */ 488 src->first = c->next; 489 if (c == src->last) src->last = NULL; 490 491 chunkqueue_append_chunk(dest, c); 492 dest->bytes_in += use; 493 } else { 494 /* partial chunk with length "use" */ 495 496 switch (c->type) { 497 case MEM_CHUNK: 498 chunkqueue_append_mem(dest, c->mem->ptr + c->offset, use); 499 break; 500 case FILE_CHUNK: 501 /* tempfile flag is in "last" chunk after the split */ 502 chunkqueue_append_file(dest, c->mem, c->file.start + c->offset, use); 503 break; 504 } 505 506 c->offset += use; 507 force_assert(0 == len); 508 } 509 510 src->bytes_out += use; 511 } 512 } 513 514 static chunk *chunkqueue_get_append_tempfile(chunkqueue * const restrict cq, log_error_st * const restrict errh) { 515 chunk *c; 516 buffer *template = buffer_init_string("/var/tmp/lighttpd-upload-XXXXXX"); 517 int fd = -1; 518 519 if (cq->tempdirs && cq->tempdirs->used) { 520 /* we have several tempdirs, only if all of them fail we jump out */ 521 522 for (errno = EIO; cq->tempdir_idx < cq->tempdirs->used; ++cq->tempdir_idx) { 523 data_string *ds = (data_string *)cq->tempdirs->data[cq->tempdir_idx]; 524 525 buffer_copy_buffer(template, &ds->value); 526 buffer_append_path_len(template, CONST_STR_LEN("lighttpd-upload-XXXXXX")); 527 if (-1 != (fd = fdevent_mkstemp_append(template->ptr))) break; 528 } 529 } else { 530 fd = fdevent_mkstemp_append(template->ptr); 531 } 532 533 if (fd < 0) { 534 /* (report only the last error to mkstemp() 535 * if multiple temp dirs attempted) */ 536 log_perror(errh, __FILE__, __LINE__, 537 "opening temp-file failed: %s", template->ptr); 538 buffer_free(template); 539 return NULL; 540 } 541 542 c = chunkqueue_append_file_chunk(cq, template, 0, 0); 543 c->file.fd = fd; 544 c->file.is_temp = 1; 545 546 buffer_free(template); 547 548 return c; 549 } 550 551 int chunkqueue_append_mem_to_tempfile(chunkqueue * const restrict dest, const char * restrict mem, size_t len, log_error_st * const restrict errh) { 552 chunk *dst_c; 553 ssize_t written; 554 555 do { 556 /* 557 * if the last chunk is 558 * - smaller than dest->upload_temp_file_size 559 * - not read yet (offset == 0) 560 * -> append to it (so it might actually become larger than dest->upload_temp_file_size) 561 * otherwise 562 * -> create a new chunk 563 * 564 * */ 565 566 dst_c = dest->last; 567 if (NULL != dst_c 568 && FILE_CHUNK == dst_c->type 569 && dst_c->file.is_temp 570 && dst_c->file.fd >= 0 571 && 0 == dst_c->offset) { 572 /* ok, take the last chunk for our job */ 573 574 if (dst_c->file.length >= (off_t)dest->upload_temp_file_size) { 575 /* the chunk is too large now, close it */ 576 int rc = close(dst_c->file.fd); 577 dst_c->file.fd = -1; 578 if (0 != rc) { 579 log_perror(errh, __FILE__, __LINE__, 580 "close() temp-file %s failed", dst_c->mem->ptr); 581 return -1; 582 } 583 dst_c = NULL; 584 } 585 } else { 586 dst_c = NULL; 587 } 588 589 if (NULL == dst_c && NULL == (dst_c = chunkqueue_get_append_tempfile(dest, errh))) { 590 return -1; 591 } 592 #ifdef __COVERITY__ 593 if (dst_c->file.fd < 0) return -1; 594 #endif 595 596 /* (dst_c->file.fd >= 0) */ 597 /* coverity[negative_returns : FALSE] */ 598 written = write(dst_c->file.fd, mem, len); 599 600 if ((size_t) written == len) { 601 dst_c->file.length += len; 602 dest->bytes_in += len; 603 604 return 0; 605 } else if (written >= 0) { 606 /*(assume EINTR if partial write and retry write(); 607 * retry write() might fail with ENOSPC if no more space on volume)*/ 608 dest->bytes_in += written; 609 mem += written; 610 len -= (size_t)written; 611 dst_c->file.length += (size_t)written; 612 /* continue; retry */ 613 } else if (errno == EINTR) { 614 /* continue; retry */ 615 } else { 616 int retry = (errno == ENOSPC && dest->tempdirs && ++dest->tempdir_idx < dest->tempdirs->used); 617 if (!retry) { 618 log_perror(errh, __FILE__, __LINE__, 619 "write() temp-file %s failed", dst_c->mem->ptr); 620 } 621 622 if (0 == chunk_remaining_length(dst_c)) { 623 /*(remove empty chunk and unlink tempfile)*/ 624 chunkqueue_remove_empty_chunks(dest); 625 } else {/*(close tempfile; avoid later attempts to append)*/ 626 int rc = close(dst_c->file.fd); 627 dst_c->file.fd = -1; 628 if (0 != rc) { 629 log_perror(errh, __FILE__, __LINE__, 630 "close() temp-file %s failed", dst_c->mem->ptr); 631 return -1; 632 } 633 } 634 if (!retry) break; /* return -1; */ 635 636 /* continue; retry */ 637 } 638 639 } while (dst_c); 640 641 return -1; 642 } 643 644 int chunkqueue_steal_with_tempfiles(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len, log_error_st * const restrict errh) { 645 while (len > 0) { 646 chunk *c = src->first; 647 off_t clen = 0, use; 648 649 if (NULL == c) break; 650 651 clen = chunk_remaining_length(c); 652 if (0 == clen) { 653 /* drop empty chunk */ 654 src->first = c->next; 655 if (c == src->last) src->last = NULL; 656 chunk_release(c); 657 continue; 658 } 659 660 use = (len >= clen) ? clen : len; 661 len -= use; 662 663 switch (c->type) { 664 case FILE_CHUNK: 665 if (use == clen) { 666 /* move complete chunk */ 667 src->first = c->next; 668 if (c == src->last) src->last = NULL; 669 chunkqueue_append_chunk(dest, c); 670 dest->bytes_in += use; 671 } else { 672 /* partial chunk with length "use" */ 673 /* tempfile flag is in "last" chunk after the split */ 674 chunkqueue_append_file(dest, c->mem, c->file.start + c->offset, use); 675 676 c->offset += use; 677 force_assert(0 == len); 678 } 679 break; 680 681 case MEM_CHUNK: 682 /* store "use" bytes from memory chunk in tempfile */ 683 if (0 != chunkqueue_append_mem_to_tempfile(dest, c->mem->ptr + c->offset, use, errh)) { 684 return -1; 685 } 686 687 if (use == clen) { 688 /* finished chunk */ 689 src->first = c->next; 690 if (c == src->last) src->last = NULL; 691 chunk_release(c); 692 } else { 693 /* partial chunk */ 694 c->offset += use; 695 force_assert(0 == len); 696 } 697 break; 698 } 699 700 src->bytes_out += use; 701 } 702 703 return 0; 704 } 705 706 off_t chunkqueue_length(chunkqueue *cq) { 707 off_t len = 0; 708 chunk *c; 709 710 for (c = cq->first; c; c = c->next) { 711 len += chunk_remaining_length(c); 712 } 713 714 return len; 715 } 716 717 void chunkqueue_mark_written(chunkqueue *cq, off_t len) { 718 cq->bytes_out += len; 719 720 for (chunk *c; (c = cq->first); ) { 721 off_t c_len = chunk_remaining_length(c); 722 if (len >= c_len) { /* chunk got finished */ 723 len -= c_len; 724 cq->first = c->next; 725 chunk_release(c); 726 if (0 == len) break; 727 } 728 else { /* partial chunk */ 729 c->offset += len; 730 return; /* chunk not finished */ 731 } 732 } 733 734 if (NULL == cq->first) 735 cq->last = NULL; 736 else 737 chunkqueue_remove_finished_chunks(cq); 738 } 739 740 void chunkqueue_remove_finished_chunks(chunkqueue *cq) { 741 for (chunk *c; (c = cq->first) && 0 == chunk_remaining_length(c); ){ 742 if (NULL == (cq->first = c->next)) cq->last = NULL; 743 chunk_release(c); 744 } 745 } 746 747 static void chunkqueue_remove_empty_chunks(chunkqueue *cq) { 748 chunk *c; 749 chunkqueue_remove_finished_chunks(cq); 750 751 for (c = cq->first; c && c->next; c = c->next) { 752 if (0 == chunk_remaining_length(c->next)) { 753 chunk *empty = c->next; 754 c->next = empty->next; 755 if (empty == cq->last) cq->last = c; 756 chunk_release(empty); 757 } 758 } 759 } 760 761 void chunkqueue_compact_mem(chunkqueue *cq, size_t clen) { 762 chunk *c = cq->first; 763 buffer *b = c->mem; 764 size_t len = buffer_string_length(b) - c->offset; 765 if (b->size > clen) { 766 if (0 != c->offset) { 767 memmove(b->ptr, b->ptr+c->offset, len); 768 buffer_string_set_length(b, len); 769 c->offset = 0; 770 } 771 } 772 else { 773 b = chunkqueue_prepend_buffer_open_sz(cq, clen + 8192); 774 buffer_append_string_len(b, c->mem->ptr + c->offset, len); 775 cq->first->next = c->next; 776 if (NULL == c->next) cq->last = cq->first; 777 chunk_release(c); 778 c = cq->first; 779 } 780 for (chunk *fc = c; ((clen -= len) && (c = fc->next)); ) { 781 len = buffer_string_length(c->mem) - c->offset; 782 if (len > clen) len = clen; 783 buffer_append_string_len(b, c->mem->ptr + c->offset, len); 784 fc->next = c->next; 785 if (NULL == c->next) cq->last = fc; 786 chunk_release(c); 787 } 788 /* chunkqueue_prepend_buffer_commit() is not called here; 789 * no data added/removed from chunkqueue; consolidated only */ 790 } 791 792 int chunkqueue_open_file_chunk(chunkqueue * const restrict cq, log_error_st * const restrict errh) { 793 chunk* const c = cq->first; 794 off_t offset, toSend; 795 struct stat st; 796 797 force_assert(NULL != c); 798 force_assert(FILE_CHUNK == c->type); 799 force_assert(c->offset >= 0 && c->offset <= c->file.length); 800 801 offset = c->file.start + c->offset; 802 toSend = c->file.length - c->offset; 803 804 if (-1 == c->file.fd) { 805 /* (permit symlinks; should already have been checked. However, TOC-TOU remains) */ 806 if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, 1, O_RDONLY, 0))) { 807 log_perror(errh, __FILE__, __LINE__, "open failed: %s",c->mem->ptr); 808 return -1; 809 } 810 } 811 812 /*(skip file size checks if file is temp file created by lighttpd)*/ 813 if (c->file.is_temp) return 0; 814 815 if (-1 == fstat(c->file.fd, &st)) { 816 log_perror(errh, __FILE__, __LINE__, "fstat failed"); 817 return -1; 818 } 819 820 if (offset > st.st_size || toSend > st.st_size || offset > st.st_size - toSend) { 821 log_error(errh, __FILE__, __LINE__, "file shrunk: %s", c->mem->ptr); 822 return -1; 823 } 824 825 return 0; 826 } 827 828 829 void 830 chunkqueue_small_resp_optim (chunkqueue * const restrict cq) 831 { 832 /*(caller must verify response is small (and non-empty) before calling)*/ 833 834 /*(optimization to use fewer syscalls to send a small response by reading 835 * small files into memory, thereby avoiding use of sendfile() and multiple 836 * calls to writev() (benefit for cleartext (non-TLS) and <= HTTP/1.1)) 837 *(If TLS, then will shortly need to be in memory for encryption anyway)*/ 838 839 /*assert(cq->first);*/ 840 chunk *c = cq->first; 841 chunk * const filec = c->next; 842 if (c->type != MEM_CHUNK || filec != cq->last || filec->type != FILE_CHUNK) 843 return; 844 845 const int fd = filec->file.fd; 846 if (fd < 0) return; /*(require that file already be open)*/ 847 off_t offset = filec->file.start + filec->offset; 848 if (0 != offset && -1 == lseek(fd, offset, SEEK_SET)) return; 849 850 /* Note: there should be no size change in chunkqueue, 851 * so cq->bytes_in and cq->bytes_out should not be modified */ 852 853 buffer *b = c->mem; 854 off_t len = filec->file.length - filec->offset; 855 if ((size_t)len > chunk_buffer_string_space(b)) { 856 chunk * const nc = chunk_acquire((size_t)len+1); 857 c->next = nc; 858 nc->next = filec; 859 b = nc->mem; 860 } 861 862 char * const ptr = b->ptr + chunk_buffer_string_length(b); 863 ssize_t rd; 864 offset = 0; /*(reuse offset var for offset into mem buffer)*/ 865 do { 866 rd = read(fd, ptr+offset, len-offset); 867 } while (rd > 0 ? (offset += rd, len -= rd) : errno == EINTR); 868 /*(contents of chunkqueue kept valid even if error reading from file)*/ 869 buffer_commit(b, offset); 870 filec->offset += offset; 871 chunkqueue_remove_empty_chunks(cq); 872 } 873