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