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 += chunkqueue_length(src); 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_update_file(chunkqueue * const restrict cq, chunk *c, off_t len) { 450 /*assert(c->type == FILE_CHUNK);*/ 451 c->file.length += len; 452 cq->bytes_in += len; 453 } 454 455 void chunkqueue_set_tempdirs_default (const array *tempdirs, off_t upload_temp_file_size) { 456 chunkqueue_default_tempdirs = tempdirs; 457 chunkqueue_default_tempfile_size 458 = (0 == upload_temp_file_size) ? DEFAULT_TEMPFILE_SIZE 459 : (upload_temp_file_size > MAX_TEMPFILE_SIZE) ? MAX_TEMPFILE_SIZE 460 : upload_temp_file_size; 461 } 462 463 void chunkqueue_set_tempdirs(chunkqueue * const restrict cq, const array * const restrict tempdirs, off_t upload_temp_file_size) { 464 force_assert(NULL != cq); 465 cq->tempdirs = tempdirs; 466 cq->upload_temp_file_size 467 = (0 == upload_temp_file_size) ? DEFAULT_TEMPFILE_SIZE 468 : (upload_temp_file_size > MAX_TEMPFILE_SIZE) ? MAX_TEMPFILE_SIZE 469 : upload_temp_file_size; 470 cq->tempdir_idx = 0; 471 } 472 473 void chunkqueue_steal(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len) { 474 while (len > 0) { 475 chunk *c = src->first; 476 off_t clen = 0, use; 477 478 if (NULL == c) break; 479 480 clen = chunk_remaining_length(c); 481 if (0 == clen) { 482 /* drop empty chunk */ 483 src->first = c->next; 484 if (c == src->last) src->last = NULL; 485 chunk_release(c); 486 continue; 487 } 488 489 use = len >= clen ? clen : len; 490 len -= use; 491 492 if (use == clen) { 493 /* move complete chunk */ 494 src->first = c->next; 495 if (c == src->last) src->last = NULL; 496 497 chunkqueue_append_chunk(dest, c); 498 dest->bytes_in += use; 499 } else { 500 /* partial chunk with length "use" */ 501 502 switch (c->type) { 503 case MEM_CHUNK: 504 chunkqueue_append_mem(dest, c->mem->ptr + c->offset, use); 505 break; 506 case FILE_CHUNK: 507 /* tempfile flag is in "last" chunk after the split */ 508 chunkqueue_append_file(dest, c->mem, c->file.start + c->offset, use); 509 break; 510 } 511 512 c->offset += use; 513 force_assert(0 == len); 514 } 515 516 src->bytes_out += use; 517 } 518 } 519 520 static chunk *chunkqueue_get_append_tempfile(chunkqueue * const restrict cq, log_error_st * const restrict errh) { 521 chunk *c; 522 buffer *template = buffer_init_string("/var/tmp/lighttpd-upload-XXXXXX"); 523 int fd = -1; 524 525 if (cq->tempdirs && cq->tempdirs->used) { 526 /* we have several tempdirs, only if all of them fail we jump out */ 527 528 for (errno = EIO; cq->tempdir_idx < cq->tempdirs->used; ++cq->tempdir_idx) { 529 data_string *ds = (data_string *)cq->tempdirs->data[cq->tempdir_idx]; 530 531 buffer_copy_buffer(template, &ds->value); 532 buffer_append_path_len(template, CONST_STR_LEN("lighttpd-upload-XXXXXX")); 533 if (-1 != (fd = fdevent_mkstemp_append(template->ptr))) break; 534 } 535 } else { 536 fd = fdevent_mkstemp_append(template->ptr); 537 } 538 539 if (fd < 0) { 540 /* (report only the last error to mkstemp() 541 * if multiple temp dirs attempted) */ 542 log_perror(errh, __FILE__, __LINE__, 543 "opening temp-file failed: %s", template->ptr); 544 buffer_free(template); 545 return NULL; 546 } 547 548 c = chunkqueue_append_file_chunk(cq, template, 0, 0); 549 c->file.fd = fd; 550 c->file.is_temp = 1; 551 552 buffer_free(template); 553 554 return c; 555 } 556 557 int chunkqueue_append_mem_to_tempfile(chunkqueue * const restrict dest, const char * restrict mem, size_t len, log_error_st * const restrict errh) { 558 chunk *dst_c; 559 ssize_t written; 560 561 do { 562 /* 563 * if the last chunk is 564 * - smaller than dest->upload_temp_file_size 565 * - not read yet (offset == 0) 566 * -> append to it (so it might actually become larger than dest->upload_temp_file_size) 567 * otherwise 568 * -> create a new chunk 569 * 570 * */ 571 572 dst_c = dest->last; 573 if (NULL != dst_c 574 && FILE_CHUNK == dst_c->type 575 && dst_c->file.is_temp 576 && dst_c->file.fd >= 0 577 && 0 == dst_c->offset) { 578 /* ok, take the last chunk for our job */ 579 580 if (dst_c->file.length >= (off_t)dest->upload_temp_file_size) { 581 /* the chunk is too large now, close it */ 582 int rc = close(dst_c->file.fd); 583 dst_c->file.fd = -1; 584 if (0 != rc) { 585 log_perror(errh, __FILE__, __LINE__, 586 "close() temp-file %s failed", dst_c->mem->ptr); 587 return -1; 588 } 589 dst_c = NULL; 590 } 591 } else { 592 dst_c = NULL; 593 } 594 595 if (NULL == dst_c && NULL == (dst_c = chunkqueue_get_append_tempfile(dest, errh))) { 596 return -1; 597 } 598 #ifdef __COVERITY__ 599 if (dst_c->file.fd < 0) return -1; 600 #endif 601 602 /* (dst_c->file.fd >= 0) */ 603 /* coverity[negative_returns : FALSE] */ 604 written = write(dst_c->file.fd, mem, len); 605 606 if ((size_t) written == len) { 607 dst_c->file.length += len; 608 dest->bytes_in += len; 609 610 return 0; 611 } else if (written >= 0) { 612 /*(assume EINTR if partial write and retry write(); 613 * retry write() might fail with ENOSPC if no more space on volume)*/ 614 dest->bytes_in += written; 615 mem += written; 616 len -= (size_t)written; 617 dst_c->file.length += (size_t)written; 618 /* continue; retry */ 619 } else if (errno == EINTR) { 620 /* continue; retry */ 621 } else { 622 int retry = (errno == ENOSPC && dest->tempdirs && ++dest->tempdir_idx < dest->tempdirs->used); 623 if (!retry) { 624 log_perror(errh, __FILE__, __LINE__, 625 "write() temp-file %s failed", dst_c->mem->ptr); 626 } 627 628 if (0 == chunk_remaining_length(dst_c)) { 629 /*(remove empty chunk and unlink tempfile)*/ 630 chunkqueue_remove_empty_chunks(dest); 631 } else {/*(close tempfile; avoid later attempts to append)*/ 632 int rc = close(dst_c->file.fd); 633 dst_c->file.fd = -1; 634 if (0 != rc) { 635 log_perror(errh, __FILE__, __LINE__, 636 "close() temp-file %s failed", dst_c->mem->ptr); 637 return -1; 638 } 639 } 640 if (!retry) break; /* return -1; */ 641 642 /* continue; retry */ 643 } 644 645 } while (dst_c); 646 647 return -1; 648 } 649 650 int chunkqueue_steal_with_tempfiles(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len, log_error_st * const restrict errh) { 651 while (len > 0) { 652 chunk *c = src->first; 653 off_t clen = 0, use; 654 655 if (NULL == c) break; 656 657 clen = chunk_remaining_length(c); 658 if (0 == clen) { 659 /* drop empty chunk */ 660 src->first = c->next; 661 if (c == src->last) src->last = NULL; 662 chunk_release(c); 663 continue; 664 } 665 666 use = (len >= clen) ? clen : len; 667 len -= use; 668 669 switch (c->type) { 670 case FILE_CHUNK: 671 if (use == clen) { 672 /* move complete chunk */ 673 src->first = c->next; 674 if (c == src->last) src->last = NULL; 675 chunkqueue_append_chunk(dest, c); 676 dest->bytes_in += use; 677 } else { 678 /* partial chunk with length "use" */ 679 /* tempfile flag is in "last" chunk after the split */ 680 chunkqueue_append_file(dest, c->mem, c->file.start + c->offset, use); 681 682 c->offset += use; 683 force_assert(0 == len); 684 } 685 break; 686 687 case MEM_CHUNK: 688 /* store "use" bytes from memory chunk in tempfile */ 689 if (0 != chunkqueue_append_mem_to_tempfile(dest, c->mem->ptr + c->offset, use, errh)) { 690 return -1; 691 } 692 693 if (use == clen) { 694 /* finished chunk */ 695 src->first = c->next; 696 if (c == src->last) src->last = NULL; 697 chunk_release(c); 698 } else { 699 /* partial chunk */ 700 c->offset += use; 701 force_assert(0 == len); 702 } 703 break; 704 } 705 706 src->bytes_out += use; 707 } 708 709 return 0; 710 } 711 712 void chunkqueue_mark_written(chunkqueue *cq, off_t len) { 713 cq->bytes_out += len; 714 715 for (chunk *c; (c = cq->first); ) { 716 off_t c_len = chunk_remaining_length(c); 717 if (len >= c_len) { /* chunk got finished */ 718 len -= c_len; 719 cq->first = c->next; 720 chunk_release(c); 721 if (0 == len) break; 722 } 723 else { /* partial chunk */ 724 c->offset += len; 725 return; /* chunk not finished */ 726 } 727 } 728 729 if (NULL == cq->first) 730 cq->last = NULL; 731 else 732 chunkqueue_remove_finished_chunks(cq); 733 } 734 735 void chunkqueue_remove_finished_chunks(chunkqueue *cq) { 736 for (chunk *c; (c = cq->first) && 0 == chunk_remaining_length(c); ){ 737 if (NULL == (cq->first = c->next)) cq->last = NULL; 738 chunk_release(c); 739 } 740 } 741 742 static void chunkqueue_remove_empty_chunks(chunkqueue *cq) { 743 chunk *c; 744 chunkqueue_remove_finished_chunks(cq); 745 746 for (c = cq->first; c && c->next; c = c->next) { 747 if (0 == chunk_remaining_length(c->next)) { 748 chunk *empty = c->next; 749 c->next = empty->next; 750 if (empty == cq->last) cq->last = c; 751 chunk_release(empty); 752 } 753 } 754 } 755 756 void chunkqueue_compact_mem(chunkqueue *cq, size_t clen) { 757 chunk *c = cq->first; 758 buffer *b = c->mem; 759 size_t len = buffer_string_length(b) - c->offset; 760 if (b->size > clen) { 761 if (0 != c->offset) { 762 memmove(b->ptr, b->ptr+c->offset, len); 763 buffer_string_set_length(b, len); 764 c->offset = 0; 765 } 766 } 767 else { 768 b = chunkqueue_prepend_buffer_open_sz(cq, clen + 8192); 769 buffer_append_string_len(b, c->mem->ptr + c->offset, len); 770 cq->first->next = c->next; 771 if (NULL == c->next) cq->last = cq->first; 772 chunk_release(c); 773 c = cq->first; 774 } 775 for (chunk *fc = c; ((clen -= len) && (c = fc->next)); ) { 776 len = buffer_string_length(c->mem) - c->offset; 777 if (len > clen) len = clen; 778 buffer_append_string_len(b, c->mem->ptr + c->offset, len); 779 fc->next = c->next; 780 if (NULL == c->next) cq->last = fc; 781 chunk_release(c); 782 } 783 /* chunkqueue_prepend_buffer_commit() is not called here; 784 * no data added/removed from chunkqueue; consolidated only */ 785 } 786 787 int chunkqueue_open_file_chunk(chunkqueue * const restrict cq, log_error_st * const restrict errh) { 788 chunk* const c = cq->first; 789 off_t offset, toSend; 790 struct stat st; 791 792 force_assert(NULL != c); 793 force_assert(FILE_CHUNK == c->type); 794 force_assert(c->offset >= 0 && c->offset <= c->file.length); 795 796 offset = c->file.start + c->offset; 797 toSend = c->file.length - c->offset; 798 799 if (-1 == c->file.fd) { 800 /* (permit symlinks; should already have been checked. However, TOC-TOU remains) */ 801 if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, 1, O_RDONLY, 0))) { 802 log_perror(errh, __FILE__, __LINE__, "open failed: %s",c->mem->ptr); 803 return -1; 804 } 805 } 806 807 /*(skip file size checks if file is temp file created by lighttpd)*/ 808 if (c->file.is_temp) return 0; 809 810 if (-1 == fstat(c->file.fd, &st)) { 811 log_perror(errh, __FILE__, __LINE__, "fstat failed"); 812 return -1; 813 } 814 815 if (offset > st.st_size || toSend > st.st_size || offset > st.st_size - toSend) { 816 log_error(errh, __FILE__, __LINE__, "file shrunk: %s", c->mem->ptr); 817 return -1; 818 } 819 820 return 0; 821 } 822 823 824 void 825 chunkqueue_small_resp_optim (chunkqueue * const restrict cq) 826 { 827 /*(caller must verify response is small (and non-empty) before calling)*/ 828 829 /*(optimization to use fewer syscalls to send a small response by reading 830 * small files into memory, thereby avoiding use of sendfile() and multiple 831 * calls to writev() (benefit for cleartext (non-TLS) and <= HTTP/1.1)) 832 *(If TLS, then will shortly need to be in memory for encryption anyway)*/ 833 834 /*assert(cq->first);*/ 835 chunk *c = cq->first; 836 chunk * const filec = c->next; 837 if (c->type != MEM_CHUNK || filec != cq->last || filec->type != FILE_CHUNK) 838 return; 839 840 const int fd = filec->file.fd; 841 if (fd < 0) return; /*(require that file already be open)*/ 842 off_t offset = filec->file.start + filec->offset; 843 if (0 != offset && -1 == lseek(fd, offset, SEEK_SET)) return; 844 845 /* Note: there should be no size change in chunkqueue, 846 * so cq->bytes_in and cq->bytes_out should not be modified */ 847 848 buffer *b = c->mem; 849 off_t len = filec->file.length - filec->offset; 850 if ((size_t)len > chunk_buffer_string_space(b)) { 851 chunk * const nc = chunk_acquire((size_t)len+1); 852 c->next = nc; 853 nc->next = filec; 854 b = nc->mem; 855 } 856 857 char * const ptr = b->ptr + chunk_buffer_string_length(b); 858 ssize_t rd; 859 offset = 0; /*(reuse offset var for offset into mem buffer)*/ 860 do { 861 rd = read(fd, ptr+offset, len-offset); 862 } while (rd > 0 ? (offset += rd, len -= rd) : errno == EINTR); 863 /*(contents of chunkqueue kept valid even if error reading from file)*/ 864 buffer_commit(b, offset); 865 filec->offset += offset; 866 chunkqueue_remove_empty_chunks(cq); 867 } 868