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(chunkqueue *cq) { 60 /* (if caller passes non-NULL cq, it must be 0-init) */ 61 if (NULL == cq) { 62 cq = calloc(1, sizeof(*cq)); 63 force_assert(NULL != cq); 64 } 65 66 cq->first = NULL; 67 cq->last = NULL; 68 69 cq->tempdirs = chunkqueue_default_tempdirs; 70 cq->upload_temp_file_size = chunkqueue_default_tempfile_size; 71 72 return cq; 73 } 74 75 __attribute_returns_nonnull__ 76 static chunk *chunk_init(size_t sz) { 77 chunk *c; 78 79 c = calloc(1, sizeof(*c)); 80 force_assert(NULL != c); 81 82 c->type = MEM_CHUNK; 83 c->mem = buffer_init(); 84 c->file.start = c->file.length = c->file.mmap.offset = 0; 85 c->file.fd = -1; 86 c->file.mmap.start = MAP_FAILED; 87 c->file.mmap.length = 0; 88 c->file.is_temp = 0; 89 c->offset = 0; 90 c->next = NULL; 91 92 buffer_string_prepare_copy(c->mem, sz-1); 93 94 return c; 95 } 96 97 static void chunk_reset_file_chunk(chunk *c) { 98 if (c->file.is_temp && !chunk_buffer_string_is_empty(c->mem)) { 99 unlink(c->mem->ptr); 100 } 101 if (c->file.fd != -1) { 102 close(c->file.fd); 103 c->file.fd = -1; 104 } 105 if (MAP_FAILED != c->file.mmap.start) { 106 munmap(c->file.mmap.start, c->file.mmap.length); 107 c->file.mmap.start = MAP_FAILED; 108 } 109 c->file.start = c->file.length = c->file.mmap.offset = 0; 110 c->file.mmap.length = 0; 111 c->file.is_temp = 0; 112 c->type = MEM_CHUNK; 113 } 114 115 static void chunk_reset(chunk *c) { 116 if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c); 117 118 buffer_clear(c->mem); 119 c->offset = 0; 120 } 121 122 static void chunk_free(chunk *c) { 123 if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c); 124 buffer_free(c->mem); 125 free(c); 126 } 127 128 buffer * chunk_buffer_acquire(void) { 129 chunk *c; 130 buffer *b; 131 if (chunks) { 132 c = chunks; 133 chunks = c->next; 134 } 135 else { 136 c = chunk_init(chunk_buf_sz); 137 } 138 c->next = chunk_buffers; 139 chunk_buffers = c; 140 b = c->mem; 141 c->mem = NULL; 142 return b; 143 } 144 145 void chunk_buffer_release(buffer *b) { 146 if (NULL == b) return; 147 if (b->size >= chunk_buf_sz && chunk_buffers) { 148 chunk *c = chunk_buffers; 149 chunk_buffers = c->next; 150 c->mem = b; 151 c->next = chunks; 152 chunks = c; 153 buffer_clear(b); 154 } 155 else { 156 buffer_free(b); 157 } 158 } 159 160 __attribute_returns_nonnull__ 161 static chunk * chunk_acquire(size_t sz) { 162 if (sz <= chunk_buf_sz) { 163 if (chunks) { 164 chunk *c = chunks; 165 chunks = c->next; 166 return c; 167 } 168 sz = chunk_buf_sz; 169 } 170 else { 171 sz = (sz + 8191) & ~8191uL; 172 /* future: might have buckets of certain sizes, up to socket buf sizes*/ 173 if (chunks_oversized && chunks_oversized->mem->size >= sz) { 174 chunk *c = chunks_oversized; 175 chunks_oversized = c->next; 176 return c; 177 } 178 } 179 180 return chunk_init(sz); 181 } 182 183 static void chunk_release(chunk *c) { 184 const size_t sz = c->mem->size; 185 if (sz == chunk_buf_sz) { 186 chunk_reset(c); 187 c->next = chunks; 188 chunks = c; 189 } 190 else if (sz > chunk_buf_sz) { 191 chunk_reset(c); 192 chunk **co = &chunks_oversized; 193 while (*co && sz < (*co)->mem->size) co = &(*co)->next; 194 c->next = *co; 195 *co = c; 196 } 197 else { 198 chunk_free(c); 199 } 200 } 201 202 void chunkqueue_chunk_pool_clear(void) 203 { 204 for (chunk *next, *c = chunks; c; c = next) { 205 next = c->next; 206 chunk_free(c); 207 } 208 chunks = NULL; 209 for (chunk *next, *c = chunks_oversized; c; c = next) { 210 next = c->next; 211 chunk_free(c); 212 } 213 chunks_oversized = NULL; 214 } 215 216 void chunkqueue_chunk_pool_free(void) 217 { 218 chunkqueue_chunk_pool_clear(); 219 for (chunk *next, *c = chunk_buffers; c; c = next) { 220 next = c->next; 221 c->mem = buffer_init(); /*(chunk_reset() expects c->mem != NULL)*/ 222 chunk_free(c); 223 } 224 chunk_buffers = NULL; 225 } 226 227 __attribute_pure__ 228 static off_t chunk_remaining_length(const chunk *c) { 229 /* MEM_CHUNK or FILE_CHUNK */ 230 return (c->type == MEM_CHUNK 231 ? (off_t)chunk_buffer_string_length(c->mem) 232 : c->file.length) 233 - c->offset; 234 } 235 236 static void chunkqueue_release_chunks(chunkqueue *cq) { 237 cq->last = NULL; 238 for (chunk *c; (c = cq->first); ) { 239 cq->first = c->next; 240 chunk_release(c); 241 } 242 } 243 244 void chunkqueue_free(chunkqueue *cq) { 245 if (NULL == cq) return; 246 chunkqueue_release_chunks(cq); 247 free(cq); 248 } 249 250 static void chunkqueue_prepend_chunk(chunkqueue * const restrict cq, chunk * const restrict c) { 251 if (NULL == (c->next = cq->first)) cq->last = c; 252 cq->first = c; 253 } 254 255 static void chunkqueue_append_chunk(chunkqueue * const restrict cq, chunk * const restrict c) { 256 c->next = NULL; 257 *(cq->last ? &cq->last->next : &cq->first) = c; 258 cq->last = c; 259 } 260 261 __attribute_returns_nonnull__ 262 static chunk * chunkqueue_prepend_mem_chunk(chunkqueue *cq, size_t sz) { 263 chunk *c = chunk_acquire(sz); 264 chunkqueue_prepend_chunk(cq, c); 265 return c; 266 } 267 268 __attribute_returns_nonnull__ 269 static chunk * chunkqueue_append_mem_chunk(chunkqueue *cq, size_t sz) { 270 chunk *c = chunk_acquire(sz); 271 chunkqueue_append_chunk(cq, c); 272 return c; 273 } 274 275 __attribute_returns_nonnull__ 276 static chunk * chunkqueue_append_file_chunk(chunkqueue * const restrict cq, const buffer * const restrict fn, off_t offset, off_t len) { 277 chunk *c = chunk_acquire(buffer_string_length(fn)+1); 278 chunkqueue_append_chunk(cq, c); 279 c->type = FILE_CHUNK; 280 c->file.start = offset; 281 c->file.length = len; 282 cq->bytes_in += len; 283 buffer_copy_buffer(c->mem, fn); 284 return c; 285 } 286 287 void chunkqueue_reset(chunkqueue *cq) { 288 chunkqueue_release_chunks(cq); 289 cq->bytes_in = 0; 290 cq->bytes_out = 0; 291 cq->tempdir_idx = 0; 292 } 293 294 void chunkqueue_append_file_fd(chunkqueue * const restrict cq, const buffer * const restrict fn, int fd, off_t offset, off_t len) { 295 if (len > 0) { 296 (chunkqueue_append_file_chunk(cq, fn, offset, len))->file.fd = fd; 297 } 298 else { 299 close(fd); 300 } 301 } 302 303 void chunkqueue_append_file(chunkqueue * const restrict cq, const buffer * const restrict fn, off_t offset, off_t len) { 304 if (len > 0) { 305 chunkqueue_append_file_chunk(cq, fn, offset, len); 306 } 307 } 308 309 310 static int chunkqueue_append_mem_extend_chunk(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) { 311 chunk *c = cq->last; 312 if (0 == len) return 1; 313 if (c != NULL && c->type == MEM_CHUNK 314 && chunk_buffer_string_space(c->mem) >= len) { 315 buffer_append_string_len(c->mem, mem, len); 316 cq->bytes_in += len; 317 return 1; 318 } 319 return 0; 320 } 321 322 323 void chunkqueue_append_buffer(chunkqueue * const restrict cq, buffer * const restrict mem) { 324 chunk *c; 325 size_t len = buffer_string_length(mem); 326 if (len < 256 && chunkqueue_append_mem_extend_chunk(cq, mem->ptr, len)) return; 327 328 c = chunkqueue_append_mem_chunk(cq, chunk_buf_sz); 329 cq->bytes_in += len; 330 buffer_move(c->mem, mem); 331 } 332 333 334 void chunkqueue_append_mem(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) { 335 chunk *c; 336 if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len)) 337 return; 338 339 c = chunkqueue_append_mem_chunk(cq, len+1); 340 cq->bytes_in += len; 341 buffer_copy_string_len(c->mem, mem, len); 342 } 343 344 345 void chunkqueue_append_mem_min(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) { 346 chunk *c; 347 if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len)) 348 return; 349 350 c = chunk_init(len+1); 351 chunkqueue_append_chunk(cq, c); 352 cq->bytes_in += len; 353 buffer_copy_string_len(c->mem, mem, len); 354 } 355 356 357 void chunkqueue_append_chunkqueue(chunkqueue * const restrict cq, chunkqueue * const restrict src) { 358 if (src == NULL || NULL == src->first) return; 359 360 if (NULL == cq->first) { 361 cq->first = src->first; 362 } else { 363 cq->last->next = src->first; 364 } 365 cq->last = src->last; 366 cq->bytes_in += chunkqueue_length(src); 367 368 src->first = NULL; 369 src->last = NULL; 370 src->bytes_out = src->bytes_in; 371 } 372 373 374 buffer * chunkqueue_prepend_buffer_open_sz(chunkqueue *cq, size_t sz) { 375 chunk * const c = chunkqueue_prepend_mem_chunk(cq, sz); 376 return c->mem; 377 } 378 379 380 buffer * chunkqueue_prepend_buffer_open(chunkqueue *cq) { 381 return chunkqueue_prepend_buffer_open_sz(cq, chunk_buf_sz); 382 } 383 384 385 void chunkqueue_prepend_buffer_commit(chunkqueue *cq) { 386 cq->bytes_in += chunk_buffer_string_length(cq->first->mem); 387 } 388 389 390 buffer * chunkqueue_append_buffer_open_sz(chunkqueue *cq, size_t sz) { 391 chunk * const c = chunkqueue_append_mem_chunk(cq, sz); 392 return c->mem; 393 } 394 395 396 buffer * chunkqueue_append_buffer_open(chunkqueue *cq) { 397 return chunkqueue_append_buffer_open_sz(cq, chunk_buf_sz); 398 } 399 400 401 void chunkqueue_append_buffer_commit(chunkqueue *cq) { 402 cq->bytes_in += chunk_buffer_string_length(cq->last->mem); 403 } 404 405 406 static void chunkqueue_remove_empty_chunks(chunkqueue *cq); 407 408 409 char * chunkqueue_get_memory(chunkqueue * const restrict cq, size_t * const restrict len) { 410 size_t sz = *len ? *len : (chunk_buf_sz >> 1); 411 buffer *b; 412 chunk *c = cq->last; 413 if (NULL != c && MEM_CHUNK == c->type) { 414 /* return pointer into existing buffer if large enough */ 415 size_t avail = chunk_buffer_string_space(c->mem); 416 if (avail >= sz) { 417 *len = avail; 418 b = c->mem; 419 return b->ptr + chunk_buffer_string_length(b); 420 } 421 } 422 423 /* allocate new chunk */ 424 b = chunkqueue_append_buffer_open_sz(cq, sz); 425 *len = chunk_buffer_string_space(b); 426 return b->ptr; 427 } 428 429 void chunkqueue_use_memory(chunkqueue * const restrict cq, chunk *ckpt, size_t len) { 430 buffer *b = cq->last->mem; 431 432 if (len > 0) { 433 buffer_commit(b, len); 434 cq->bytes_in += len; 435 if (cq->last == ckpt || NULL == ckpt || MEM_CHUNK != ckpt->type 436 || len > chunk_buffer_string_space(ckpt->mem)) return; 437 438 buffer_append_string_buffer(ckpt->mem, b); 439 } 440 else if (!chunk_buffer_string_is_empty(b)) { /*(cq->last == ckpt)*/ 441 return; /* last chunk is not empty */ 442 } 443 444 /* remove empty last chunk */ 445 chunk_release(cq->last); 446 cq->last = ckpt; 447 *(ckpt ? &ckpt->next : &cq->first) = NULL; 448 } 449 450 void chunkqueue_update_file(chunkqueue * const restrict cq, chunk *c, off_t len) { 451 /*assert(c->type == FILE_CHUNK);*/ 452 c->file.length += len; 453 cq->bytes_in += len; 454 } 455 456 void chunkqueue_set_tempdirs_default (const array *tempdirs, off_t upload_temp_file_size) { 457 chunkqueue_default_tempdirs = tempdirs; 458 chunkqueue_default_tempfile_size 459 = (0 == upload_temp_file_size) ? DEFAULT_TEMPFILE_SIZE 460 : (upload_temp_file_size > MAX_TEMPFILE_SIZE) ? MAX_TEMPFILE_SIZE 461 : upload_temp_file_size; 462 } 463 464 void chunkqueue_set_tempdirs(chunkqueue * const restrict cq, const array * const restrict tempdirs, off_t upload_temp_file_size) { 465 force_assert(NULL != cq); 466 cq->tempdirs = tempdirs; 467 cq->upload_temp_file_size 468 = (0 == upload_temp_file_size) ? DEFAULT_TEMPFILE_SIZE 469 : (upload_temp_file_size > MAX_TEMPFILE_SIZE) ? MAX_TEMPFILE_SIZE 470 : upload_temp_file_size; 471 cq->tempdir_idx = 0; 472 } 473 474 void chunkqueue_steal(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len) { 475 while (len > 0) { 476 chunk *c = src->first; 477 off_t clen = 0, use; 478 479 if (NULL == c) break; 480 481 clen = chunk_remaining_length(c); 482 if (0 == clen) { 483 /* drop empty chunk */ 484 src->first = c->next; 485 if (c == src->last) src->last = NULL; 486 chunk_release(c); 487 continue; 488 } 489 490 use = len >= clen ? clen : len; 491 len -= use; 492 493 if (use == clen) { 494 /* move complete chunk */ 495 src->first = c->next; 496 if (c == src->last) src->last = NULL; 497 498 chunkqueue_append_chunk(dest, c); 499 dest->bytes_in += use; 500 } else { 501 /* partial chunk with length "use" */ 502 503 switch (c->type) { 504 case MEM_CHUNK: 505 chunkqueue_append_mem(dest, c->mem->ptr + c->offset, use); 506 break; 507 case FILE_CHUNK: 508 /* tempfile flag is in "last" chunk after the split */ 509 chunkqueue_append_file(dest, c->mem, c->file.start + c->offset, use); 510 if (c->file.fd >= 0) 511 dest->last->file.fd = fdevent_dup_cloexec(c->file.fd); 512 break; 513 } 514 515 c->offset += use; 516 force_assert(0 == len); 517 } 518 519 src->bytes_out += use; 520 } 521 } 522 523 static chunk *chunkqueue_get_append_tempfile(chunkqueue * const restrict cq, log_error_st * const restrict errh) { 524 chunk *c; 525 buffer *template = buffer_init_string("/var/tmp/lighttpd-upload-XXXXXX"); 526 int fd = -1; 527 528 if (cq->tempdirs && cq->tempdirs->used) { 529 /* we have several tempdirs, only if all of them fail we jump out */ 530 531 for (errno = EIO; cq->tempdir_idx < cq->tempdirs->used; ++cq->tempdir_idx) { 532 data_string *ds = (data_string *)cq->tempdirs->data[cq->tempdir_idx]; 533 534 buffer_copy_buffer(template, &ds->value); 535 buffer_append_path_len(template, CONST_STR_LEN("lighttpd-upload-XXXXXX")); 536 if (-1 != (fd = fdevent_mkstemp_append(template->ptr))) break; 537 } 538 } else { 539 fd = fdevent_mkstemp_append(template->ptr); 540 } 541 542 if (fd < 0) { 543 /* (report only the last error to mkstemp() 544 * if multiple temp dirs attempted) */ 545 log_perror(errh, __FILE__, __LINE__, 546 "opening temp-file failed: %s", template->ptr); 547 buffer_free(template); 548 return NULL; 549 } 550 551 c = chunkqueue_append_file_chunk(cq, template, 0, 0); 552 c->file.fd = fd; 553 c->file.is_temp = 1; 554 555 buffer_free(template); 556 557 return c; 558 } 559 560 int chunkqueue_append_mem_to_tempfile(chunkqueue * const restrict dest, const char * restrict mem, size_t len, log_error_st * const restrict errh) { 561 chunk *dst_c; 562 ssize_t written; 563 564 do { 565 /* 566 * if the last chunk is 567 * - smaller than dest->upload_temp_file_size 568 * - not read yet (offset == 0) 569 * -> append to it (so it might actually become larger than dest->upload_temp_file_size) 570 * otherwise 571 * -> create a new chunk 572 * 573 * */ 574 575 dst_c = dest->last; 576 if (NULL != dst_c 577 && FILE_CHUNK == dst_c->type 578 && dst_c->file.is_temp 579 && dst_c->file.fd >= 0 580 && 0 == dst_c->offset) { 581 /* ok, take the last chunk for our job */ 582 583 if (dst_c->file.length >= (off_t)dest->upload_temp_file_size) { 584 /* the chunk is too large now, close it */ 585 int rc = close(dst_c->file.fd); 586 dst_c->file.fd = -1; 587 if (0 != rc) { 588 log_perror(errh, __FILE__, __LINE__, 589 "close() temp-file %s failed", dst_c->mem->ptr); 590 return -1; 591 } 592 dst_c = NULL; 593 } 594 } else { 595 dst_c = NULL; 596 } 597 598 if (NULL == dst_c && NULL == (dst_c = chunkqueue_get_append_tempfile(dest, errh))) { 599 return -1; 600 } 601 #ifdef __COVERITY__ 602 if (dst_c->file.fd < 0) return -1; 603 #endif 604 605 /* (dst_c->file.fd >= 0) */ 606 /* coverity[negative_returns : FALSE] */ 607 written = write(dst_c->file.fd, mem, len); 608 609 if ((size_t) written == len) { 610 dst_c->file.length += len; 611 dest->bytes_in += len; 612 613 return 0; 614 } else if (written >= 0) { 615 /*(assume EINTR if partial write and retry write(); 616 * retry write() might fail with ENOSPC if no more space on volume)*/ 617 dest->bytes_in += written; 618 mem += written; 619 len -= (size_t)written; 620 dst_c->file.length += (size_t)written; 621 /* continue; retry */ 622 } else if (errno == EINTR) { 623 /* continue; retry */ 624 } else { 625 int retry = (errno == ENOSPC && dest->tempdirs && ++dest->tempdir_idx < dest->tempdirs->used); 626 if (!retry) { 627 log_perror(errh, __FILE__, __LINE__, 628 "write() temp-file %s failed", dst_c->mem->ptr); 629 } 630 631 if (0 == chunk_remaining_length(dst_c)) { 632 /*(remove empty chunk and unlink tempfile)*/ 633 chunkqueue_remove_empty_chunks(dest); 634 } else {/*(close tempfile; avoid later attempts to append)*/ 635 int rc = close(dst_c->file.fd); 636 dst_c->file.fd = -1; 637 if (0 != rc) { 638 log_perror(errh, __FILE__, __LINE__, 639 "close() temp-file %s failed", dst_c->mem->ptr); 640 return -1; 641 } 642 } 643 if (!retry) break; /* return -1; */ 644 645 /* continue; retry */ 646 } 647 648 } while (dst_c); 649 650 return -1; 651 } 652 653 int chunkqueue_steal_with_tempfiles(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len, log_error_st * const restrict errh) { 654 while (len > 0) { 655 chunk *c = src->first; 656 off_t clen = 0, use; 657 658 if (NULL == c) break; 659 660 clen = chunk_remaining_length(c); 661 if (0 == clen) { 662 /* drop empty chunk */ 663 src->first = c->next; 664 if (c == src->last) src->last = NULL; 665 chunk_release(c); 666 continue; 667 } 668 669 use = (len >= clen) ? clen : len; 670 len -= use; 671 672 switch (c->type) { 673 case FILE_CHUNK: 674 if (use == clen) { 675 /* move complete chunk */ 676 src->first = c->next; 677 if (c == src->last) src->last = NULL; 678 chunkqueue_append_chunk(dest, c); 679 dest->bytes_in += use; 680 } else { 681 /* partial chunk with length "use" */ 682 /* tempfile flag is in "last" chunk after the split */ 683 chunkqueue_append_file(dest, c->mem, c->file.start + c->offset, use); 684 if (c->file.fd >= 0) 685 dest->last->file.fd = fdevent_dup_cloexec(c->file.fd); 686 687 c->offset += use; 688 force_assert(0 == len); 689 } 690 break; 691 692 case MEM_CHUNK: 693 /* store "use" bytes from memory chunk in tempfile */ 694 if (0 != chunkqueue_append_mem_to_tempfile(dest, c->mem->ptr + c->offset, use, errh)) { 695 return -1; 696 } 697 698 if (use == clen) { 699 /* finished chunk */ 700 src->first = c->next; 701 if (c == src->last) src->last = NULL; 702 chunk_release(c); 703 } else { 704 /* partial chunk */ 705 c->offset += use; 706 force_assert(0 == len); 707 } 708 break; 709 } 710 711 src->bytes_out += use; 712 } 713 714 return 0; 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 static int chunk_open_file_chunk(chunk * const restrict c, log_error_st * const restrict errh) { 793 off_t offset, toSend; 794 struct stat st; 795 796 force_assert(NULL != c); 797 force_assert(FILE_CHUNK == c->type); 798 force_assert(c->offset >= 0 && c->offset <= c->file.length); 799 800 offset = c->file.start + c->offset; 801 toSend = c->file.length - c->offset; 802 803 if (-1 == c->file.fd) { 804 /* (permit symlinks; should already have been checked. However, TOC-TOU remains) */ 805 if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, 1, O_RDONLY, 0))) { 806 log_perror(errh, __FILE__, __LINE__, "open failed: %s",c->mem->ptr); 807 return -1; 808 } 809 } 810 811 /*(skip file size checks if file is temp file created by lighttpd)*/ 812 if (c->file.is_temp) return 0; 813 814 if (-1 == fstat(c->file.fd, &st)) { 815 log_perror(errh, __FILE__, __LINE__, "fstat failed"); 816 return -1; 817 } 818 819 if (offset > st.st_size || toSend > st.st_size || offset > st.st_size - toSend) { 820 log_error(errh, __FILE__, __LINE__, "file shrunk: %s", c->mem->ptr); 821 return -1; 822 } 823 824 return 0; 825 } 826 827 int chunkqueue_open_file_chunk(chunkqueue * const restrict cq, log_error_st * const restrict errh) { 828 return chunk_open_file_chunk(cq->first, errh); 829 } 830 831 832 void 833 chunkqueue_small_resp_optim (chunkqueue * const restrict cq) 834 { 835 /*(caller must verify response is small (and non-empty) before calling)*/ 836 837 /*(optimization to use fewer syscalls to send a small response by reading 838 * small files into memory, thereby avoiding use of sendfile() and multiple 839 * calls to writev() (benefit for cleartext (non-TLS) and <= HTTP/1.1)) 840 *(If TLS, then will shortly need to be in memory for encryption anyway)*/ 841 842 /*assert(cq->first);*/ 843 chunk *c = cq->first; 844 chunk * const filec = c->next; 845 if (c->type != MEM_CHUNK || filec != cq->last || filec->type != FILE_CHUNK) 846 return; 847 848 const int fd = filec->file.fd; 849 if (fd < 0) return; /*(require that file already be open)*/ 850 off_t offset = filec->file.start + filec->offset; 851 if (0 != offset && -1 == lseek(fd, offset, SEEK_SET)) return; 852 853 /* Note: there should be no size change in chunkqueue, 854 * so cq->bytes_in and cq->bytes_out should not be modified */ 855 856 buffer *b = c->mem; 857 off_t len = filec->file.length - filec->offset; 858 if ((size_t)len > chunk_buffer_string_space(b)) { 859 chunk * const nc = chunk_acquire((size_t)len+1); 860 c->next = nc; 861 nc->next = filec; 862 b = nc->mem; 863 } 864 865 char * const ptr = b->ptr + chunk_buffer_string_length(b); 866 ssize_t rd; 867 offset = 0; /*(reuse offset var for offset into mem buffer)*/ 868 do { 869 rd = read(fd, ptr+offset, len-offset); 870 } while (rd > 0 ? (offset += rd, len -= rd) : errno == EINTR); 871 /*(contents of chunkqueue kept valid even if error reading from file)*/ 872 buffer_commit(b, offset); 873 filec->offset += offset; 874 chunkqueue_remove_empty_chunks(cq); 875 } 876 877 878 int 879 chunkqueue_peek_data (chunkqueue * const cq, 880 char ** const data, uint32_t * const dlen, 881 log_error_st * const errh) 882 { 883 char * const data_in = *data; 884 const uint32_t data_insz = *dlen; 885 *dlen = 0; 886 887 for (chunk *c = cq->first; c; ) { 888 uint32_t space = data_insz - *dlen; 889 switch (c->type) { 890 case MEM_CHUNK: 891 { 892 uint32_t have = buffer_string_length(c->mem) - (uint32_t)c->offset; 893 if (have > space) 894 have = space; 895 if (*dlen) 896 memcpy(data_in + *dlen, c->mem->ptr + c->offset, have); 897 else 898 *data = c->mem->ptr + c->offset; /*(reference; defer copy)*/ 899 *dlen += have; 900 break; 901 } 902 903 case FILE_CHUNK: 904 if (c->file.fd >= 0 || 0 == chunk_open_file_chunk(c, errh)) { 905 off_t offset = c->file.start + c->offset; 906 off_t toSend = c->file.length - c->offset; 907 if (toSend > (off_t)space) 908 toSend = (off_t)space; 909 910 if (-1 == lseek(c->file.fd, offset, SEEK_SET)) { 911 log_perror(errh, __FILE__, __LINE__, "lseek"); 912 return -1; 913 } 914 toSend = read(c->file.fd, data_in + *dlen, (size_t)toSend); 915 if (toSend <= 0) { /* -1 error; 0 EOF (unexpected) */ 916 log_perror(errh, __FILE__, __LINE__, "read"); 917 return -1; 918 } 919 920 *dlen += (uint32_t)toSend; 921 break; 922 } 923 return -1; 924 925 default: 926 return -1; 927 } 928 929 if (*dlen == data_insz) 930 break; 931 932 c = c->next; 933 if (NULL == c) 934 break; 935 936 if (*dlen && *data != data_in) { 937 memcpy(data_in, *data, *dlen); 938 *data = data_in; 939 } 940 } 941 942 return 0; 943 } 944 945 946 int 947 chunkqueue_read_data (chunkqueue * const cq, 948 char * const data, const uint32_t dlen, 949 log_error_st * const errh) 950 { 951 char *ptr = data; 952 uint32_t len = dlen; 953 if (chunkqueue_peek_data(cq, &ptr, &len, errh) < 0 || len != dlen) 954 return -1; 955 if (data != ptr) memcpy(data, ptr, len); 956 chunkqueue_mark_written(cq, len); 957 return 0; 958 } 959 960 961 buffer * 962 chunkqueue_read_squash (chunkqueue * const restrict cq, log_error_st * const restrict errh) 963 { 964 /* read and replace chunkqueue contents with single MEM_CHUNK. 965 * cq->bytes_out is not modified */ 966 967 off_t cqlen = chunkqueue_length(cq); 968 if (cqlen >= UINT32_MAX) return NULL; 969 970 if (cq->first && NULL == cq->first->next && cq->first->type == MEM_CHUNK) 971 return cq->first->mem; 972 973 chunk * const c = chunk_acquire((uint32_t)cqlen+1); 974 char *data = c->mem->ptr; 975 uint32_t dlen = (uint32_t)cqlen; 976 int rc = chunkqueue_peek_data(cq, &data, &dlen, errh); 977 if (rc < 0) { 978 chunk_release(c); 979 return NULL; 980 } 981 buffer_string_set_length(c->mem, dlen); 982 983 chunkqueue_release_chunks(cq); 984 chunkqueue_append_chunk(cq, c); 985 return c->mem; 986 } 987