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.fd = c->file.fd; 486 d->file.ref = c->file.ref; 487 d->file.refchg = c->file.refchg; 488 d->file.refchg(d->file.ref, 1); 489 } 490 else 491 d->file.fd = fdevent_dup_cloexec(c->file.fd); 492 } 493 } 494 495 void chunkqueue_steal(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len) { 496 while (len > 0) { 497 chunk *c = src->first; 498 off_t clen = 0, use; 499 500 if (NULL == c) break; 501 502 clen = chunk_remaining_length(c); 503 if (0 == clen) { 504 /* drop empty chunk */ 505 src->first = c->next; 506 if (c == src->last) src->last = NULL; 507 chunk_release(c); 508 continue; 509 } 510 511 use = len >= clen ? clen : len; 512 len -= use; 513 514 if (use == clen) { 515 /* move complete chunk */ 516 src->first = c->next; 517 if (c == src->last) src->last = NULL; 518 519 chunkqueue_append_chunk(dest, c); 520 dest->bytes_in += use; 521 } else { 522 /* partial chunk with length "use" */ 523 524 switch (c->type) { 525 case MEM_CHUNK: 526 chunkqueue_append_mem(dest, c->mem->ptr + c->offset, use); 527 break; 528 case FILE_CHUNK: 529 /* tempfile flag is in "last" chunk after the split */ 530 chunkqueue_steal_partial_file_chunk(dest, c, use); 531 break; 532 } 533 534 c->offset += use; 535 force_assert(0 == len); 536 } 537 538 src->bytes_out += use; 539 } 540 } 541 542 static chunk *chunkqueue_get_append_tempfile(chunkqueue * const restrict cq, log_error_st * const restrict errh) { 543 chunk *c; 544 buffer *template = buffer_init_string("/var/tmp/lighttpd-upload-XXXXXX"); 545 int fd = -1; 546 547 if (cq->tempdirs && cq->tempdirs->used) { 548 /* we have several tempdirs, only if all of them fail we jump out */ 549 550 for (errno = EIO; cq->tempdir_idx < cq->tempdirs->used; ++cq->tempdir_idx) { 551 data_string *ds = (data_string *)cq->tempdirs->data[cq->tempdir_idx]; 552 553 buffer_copy_buffer(template, &ds->value); 554 buffer_append_path_len(template, CONST_STR_LEN("lighttpd-upload-XXXXXX")); 555 if (-1 != (fd = fdevent_mkstemp_append(template->ptr))) break; 556 } 557 } else { 558 fd = fdevent_mkstemp_append(template->ptr); 559 } 560 561 if (fd < 0) { 562 /* (report only the last error to mkstemp() 563 * if multiple temp dirs attempted) */ 564 log_perror(errh, __FILE__, __LINE__, 565 "opening temp-file failed: %s", template->ptr); 566 buffer_free(template); 567 return NULL; 568 } 569 570 c = chunkqueue_append_file_chunk(cq, template, 0, 0); 571 c->file.fd = fd; 572 c->file.is_temp = 1; 573 574 buffer_free(template); 575 576 return c; 577 } 578 579 int chunkqueue_append_mem_to_tempfile(chunkqueue * const restrict dest, const char * restrict mem, size_t len, log_error_st * const restrict errh) { 580 chunk *dst_c; 581 ssize_t written; 582 583 do { 584 /* 585 * if the last chunk is 586 * - smaller than dest->upload_temp_file_size 587 * - not read yet (offset == 0) 588 * -> append to it (so it might actually become larger than dest->upload_temp_file_size) 589 * otherwise 590 * -> create a new chunk 591 * 592 * */ 593 594 dst_c = dest->last; 595 if (NULL != dst_c 596 && FILE_CHUNK == dst_c->type 597 && dst_c->file.is_temp 598 && dst_c->file.fd >= 0 599 && 0 == dst_c->offset) { 600 /* ok, take the last chunk for our job */ 601 602 if (dst_c->file.length >= (off_t)dest->upload_temp_file_size) { 603 /* the chunk is too large now, close it */ 604 force_assert(0 == dst_c->file.refchg); /*(else should not happen)*/ 605 int rc = close(dst_c->file.fd); 606 dst_c->file.fd = -1; 607 if (0 != rc) { 608 log_perror(errh, __FILE__, __LINE__, 609 "close() temp-file %s failed", dst_c->mem->ptr); 610 return -1; 611 } 612 dst_c = NULL; 613 } 614 } else { 615 dst_c = NULL; 616 } 617 618 if (NULL == dst_c && NULL == (dst_c = chunkqueue_get_append_tempfile(dest, errh))) { 619 return -1; 620 } 621 #ifdef __COVERITY__ 622 if (dst_c->file.fd < 0) return -1; 623 #endif 624 625 /* (dst_c->file.fd >= 0) */ 626 /* coverity[negative_returns : FALSE] */ 627 written = write(dst_c->file.fd, mem, len); 628 629 if ((size_t) written == len) { 630 dst_c->file.length += len; 631 dest->bytes_in += len; 632 633 return 0; 634 } else if (written >= 0) { 635 /*(assume EINTR if partial write and retry write(); 636 * retry write() might fail with ENOSPC if no more space on volume)*/ 637 dest->bytes_in += written; 638 mem += written; 639 len -= (size_t)written; 640 dst_c->file.length += (size_t)written; 641 /* continue; retry */ 642 } else if (errno == EINTR) { 643 /* continue; retry */ 644 } else { 645 int retry = (errno == ENOSPC && dest->tempdirs && ++dest->tempdir_idx < dest->tempdirs->used); 646 if (!retry) { 647 log_perror(errh, __FILE__, __LINE__, 648 "write() temp-file %s failed", dst_c->mem->ptr); 649 } 650 651 if (0 == chunk_remaining_length(dst_c)) { 652 /*(remove empty chunk and unlink tempfile)*/ 653 chunkqueue_remove_empty_chunks(dest); 654 } else {/*(close tempfile; avoid later attempts to append)*/ 655 force_assert(0 == dst_c->file.refchg); /*(else should not happen)*/ 656 int rc = close(dst_c->file.fd); 657 dst_c->file.fd = -1; 658 if (0 != rc) { 659 log_perror(errh, __FILE__, __LINE__, 660 "close() temp-file %s failed", dst_c->mem->ptr); 661 return -1; 662 } 663 } 664 if (!retry) break; /* return -1; */ 665 666 /* continue; retry */ 667 } 668 669 } while (dst_c); 670 671 return -1; 672 } 673 674 int chunkqueue_steal_with_tempfiles(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len, log_error_st * const restrict errh) { 675 while (len > 0) { 676 chunk *c = src->first; 677 off_t clen = 0, use; 678 679 if (NULL == c) break; 680 681 clen = chunk_remaining_length(c); 682 if (0 == clen) { 683 /* drop empty chunk */ 684 src->first = c->next; 685 if (c == src->last) src->last = NULL; 686 chunk_release(c); 687 continue; 688 } 689 690 use = (len >= clen) ? clen : len; 691 len -= use; 692 693 switch (c->type) { 694 case FILE_CHUNK: 695 if (use == clen) { 696 /* move complete chunk */ 697 src->first = c->next; 698 if (c == src->last) src->last = NULL; 699 chunkqueue_append_chunk(dest, c); 700 dest->bytes_in += use; 701 } else { 702 /* partial chunk with length "use" */ 703 /* tempfile flag is in "last" chunk after the split */ 704 chunkqueue_steal_partial_file_chunk(dest, c, use); 705 c->offset += use; 706 force_assert(0 == len); 707 } 708 break; 709 710 case MEM_CHUNK: 711 /* store "use" bytes from memory chunk in tempfile */ 712 if (0 != chunkqueue_append_mem_to_tempfile(dest, c->mem->ptr + c->offset, use, errh)) { 713 return -1; 714 } 715 716 if (use == clen) { 717 /* finished chunk */ 718 src->first = c->next; 719 if (c == src->last) src->last = NULL; 720 chunk_release(c); 721 } else { 722 /* partial chunk */ 723 c->offset += use; 724 force_assert(0 == len); 725 } 726 break; 727 } 728 729 src->bytes_out += use; 730 } 731 732 return 0; 733 } 734 735 void chunkqueue_mark_written(chunkqueue *cq, off_t len) { 736 cq->bytes_out += len; 737 738 for (chunk *c; (c = cq->first); ) { 739 off_t c_len = chunk_remaining_length(c); 740 if (len >= c_len) { /* chunk got finished */ 741 len -= c_len; 742 cq->first = c->next; 743 chunk_release(c); 744 if (0 == len) break; 745 } 746 else { /* partial chunk */ 747 c->offset += len; 748 return; /* chunk not finished */ 749 } 750 } 751 752 if (NULL == cq->first) 753 cq->last = NULL; 754 else 755 chunkqueue_remove_finished_chunks(cq); 756 } 757 758 void chunkqueue_remove_finished_chunks(chunkqueue *cq) { 759 for (chunk *c; (c = cq->first) && 0 == chunk_remaining_length(c); ){ 760 if (NULL == (cq->first = c->next)) cq->last = NULL; 761 chunk_release(c); 762 } 763 } 764 765 static void chunkqueue_remove_empty_chunks(chunkqueue *cq) { 766 chunk *c; 767 chunkqueue_remove_finished_chunks(cq); 768 769 for (c = cq->first; c && c->next; c = c->next) { 770 if (0 == chunk_remaining_length(c->next)) { 771 chunk *empty = c->next; 772 c->next = empty->next; 773 if (empty == cq->last) cq->last = c; 774 chunk_release(empty); 775 } 776 } 777 } 778 779 void chunkqueue_compact_mem_offset(chunkqueue * const cq) { 780 chunk * const restrict c = cq->first; 781 if (0 == c->offset) return; 782 if (c->type != MEM_CHUNK) return; /*(should not happen)*/ 783 784 buffer * const restrict b = c->mem; 785 size_t len = chunk_buffer_string_length(b) - c->offset; 786 memmove(b->ptr, b->ptr+c->offset, len); 787 c->offset = 0; 788 buffer_string_set_length(b, len); 789 } 790 791 void chunkqueue_compact_mem(chunkqueue *cq, size_t clen) { 792 /* caller must guarantee that chunks in chunkqueue are MEM_CHUNK, 793 * which is currently always true when reading input from client */ 794 chunk *c = cq->first; 795 buffer *b = c->mem; 796 size_t len = chunk_buffer_string_length(b) - c->offset; 797 if (len <= clen) return; 798 if (b->size > clen) { 799 if (chunk_buffer_string_space(b) < clen - len) 800 chunkqueue_compact_mem_offset(cq); 801 } 802 else { 803 b = chunkqueue_prepend_buffer_open_sz(cq, clen+1); 804 buffer_append_string_len(b, c->mem->ptr + c->offset, len); 805 cq->first->next = c->next; 806 if (NULL == c->next) cq->last = cq->first; 807 chunk_release(c); 808 c = cq->first; 809 } 810 811 for (chunk *fc = c; ((clen -= len) && (c = fc->next)); ) { 812 len = chunk_buffer_string_length(c->mem) - c->offset; 813 if (len > clen) { 814 buffer_append_string_len(b, c->mem->ptr + c->offset, clen); 815 c->offset += clen; 816 break; 817 } 818 buffer_append_string_len(b, c->mem->ptr + c->offset, len); 819 fc->next = c->next; 820 if (NULL == c->next) cq->last = fc; 821 chunk_release(c); 822 } 823 /* chunkqueue_prepend_buffer_commit() is not called here; 824 * no data added/removed from chunkqueue; consolidated only */ 825 } 826 827 static int chunk_open_file_chunk(chunk * const restrict c, log_error_st * const restrict errh) { 828 off_t offset, toSend; 829 struct stat st; 830 831 force_assert(NULL != c); 832 force_assert(FILE_CHUNK == c->type); 833 force_assert(c->offset >= 0 && c->offset <= c->file.length); 834 835 offset = c->offset; 836 toSend = c->file.length - c->offset; 837 838 if (-1 == c->file.fd) { 839 /* (permit symlinks; should already have been checked. However, TOC-TOU remains) */ 840 if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, 1, O_RDONLY, 0))) { 841 log_perror(errh, __FILE__, __LINE__, "open failed: %s",c->mem->ptr); 842 return -1; 843 } 844 } 845 846 /*(skip file size checks if file is temp file created by lighttpd)*/ 847 if (c->file.is_temp) return 0; 848 849 if (-1 == fstat(c->file.fd, &st)) { 850 log_perror(errh, __FILE__, __LINE__, "fstat failed"); 851 return -1; 852 } 853 854 if (offset > st.st_size || toSend > st.st_size || offset > st.st_size - toSend) { 855 log_error(errh, __FILE__, __LINE__, "file shrunk: %s", c->mem->ptr); 856 return -1; 857 } 858 859 return 0; 860 } 861 862 int chunkqueue_open_file_chunk(chunkqueue * const restrict cq, log_error_st * const restrict errh) { 863 return chunk_open_file_chunk(cq->first, errh); 864 } 865 866 867 void 868 chunkqueue_small_resp_optim (chunkqueue * const restrict cq) 869 { 870 /*(caller must verify response is small (and non-empty) before calling)*/ 871 872 /*(optimization to use fewer syscalls to send a small response by reading 873 * small files into memory, thereby avoiding use of sendfile() and multiple 874 * calls to writev() (benefit for cleartext (non-TLS) and <= HTTP/1.1)) 875 *(If TLS, then will shortly need to be in memory for encryption anyway)*/ 876 877 /*assert(cq->first);*/ 878 chunk *c = cq->first; 879 chunk * const filec = c->next; 880 if (c->type != MEM_CHUNK || filec != cq->last || filec->type != FILE_CHUNK) 881 return; 882 883 const int fd = filec->file.fd; 884 if (fd < 0) return; /*(require that file already be open)*/ 885 off_t offset = filec->offset; 886 if (-1 == lseek(fd, offset, SEEK_SET)) return; 887 888 /* Note: there should be no size change in chunkqueue, 889 * so cq->bytes_in and cq->bytes_out should not be modified */ 890 891 buffer *b = c->mem; 892 off_t len = filec->file.length - filec->offset; 893 if ((size_t)len > chunk_buffer_string_space(b)) { 894 chunk * const nc = chunk_acquire((size_t)len+1); 895 c->next = nc; 896 nc->next = filec; 897 b = nc->mem; 898 } 899 900 char * const ptr = b->ptr + chunk_buffer_string_length(b); 901 ssize_t rd; 902 offset = 0; /*(reuse offset var for offset into mem buffer)*/ 903 do { 904 rd = read(fd, ptr+offset, len-offset); 905 } while (rd > 0 ? (offset += rd, len -= rd) : errno == EINTR); 906 /*(contents of chunkqueue kept valid even if error reading from file)*/ 907 buffer_commit(b, offset); 908 filec->offset += offset; 909 chunkqueue_remove_empty_chunks(cq); 910 } 911 912 913 int 914 chunkqueue_peek_data (chunkqueue * const cq, 915 char ** const data, uint32_t * const dlen, 916 log_error_st * const errh) 917 { 918 char * const data_in = *data; 919 const uint32_t data_insz = *dlen; 920 *dlen = 0; 921 922 for (chunk *c = cq->first; c; ) { 923 uint32_t space = data_insz - *dlen; 924 switch (c->type) { 925 case MEM_CHUNK: 926 { 927 uint32_t have = 928 chunk_buffer_string_length(c->mem) - (uint32_t)c->offset; 929 if (have > space) 930 have = space; 931 if (*dlen) 932 memcpy(data_in + *dlen, c->mem->ptr + c->offset, have); 933 else 934 *data = c->mem->ptr + c->offset; /*(reference; defer copy)*/ 935 *dlen += have; 936 break; 937 } 938 939 case FILE_CHUNK: 940 if (c->file.fd >= 0 || 0 == chunk_open_file_chunk(c, errh)) { 941 off_t offset = c->offset; 942 off_t toSend = c->file.length - c->offset; 943 if (toSend > (off_t)space) 944 toSend = (off_t)space; 945 946 if (-1 == lseek(c->file.fd, offset, SEEK_SET)) { 947 log_perror(errh, __FILE__, __LINE__, "lseek"); 948 return -1; 949 } 950 toSend = read(c->file.fd, data_in + *dlen, (size_t)toSend); 951 if (toSend <= 0) { /* -1 error; 0 EOF (unexpected) */ 952 log_perror(errh, __FILE__, __LINE__, "read"); 953 return -1; 954 } 955 956 *dlen += (uint32_t)toSend; 957 break; 958 } 959 return -1; 960 961 default: 962 return -1; 963 } 964 965 if (*dlen == data_insz) 966 break; 967 968 c = c->next; 969 if (NULL == c) 970 break; 971 972 if (*dlen && *data != data_in) { 973 memcpy(data_in, *data, *dlen); 974 *data = data_in; 975 } 976 } 977 978 return 0; 979 } 980 981 982 int 983 chunkqueue_read_data (chunkqueue * const cq, 984 char * const data, const uint32_t dlen, 985 log_error_st * const errh) 986 { 987 char *ptr = data; 988 uint32_t len = dlen; 989 if (chunkqueue_peek_data(cq, &ptr, &len, errh) < 0 || len != dlen) 990 return -1; 991 if (data != ptr) memcpy(data, ptr, len); 992 chunkqueue_mark_written(cq, len); 993 return 0; 994 } 995 996 997 buffer * 998 chunkqueue_read_squash (chunkqueue * const restrict cq, log_error_st * const restrict errh) 999 { 1000 /* read and replace chunkqueue contents with single MEM_CHUNK. 1001 * cq->bytes_out is not modified */ 1002 1003 off_t cqlen = chunkqueue_length(cq); 1004 if (cqlen >= UINT32_MAX) return NULL; 1005 1006 if (cq->first && NULL == cq->first->next && cq->first->type == MEM_CHUNK) 1007 return cq->first->mem; 1008 1009 chunk * const c = chunk_acquire((uint32_t)cqlen+1); 1010 char *data = c->mem->ptr; 1011 uint32_t dlen = (uint32_t)cqlen; 1012 int rc = chunkqueue_peek_data(cq, &data, &dlen, errh); 1013 if (rc < 0) { 1014 chunk_release(c); 1015 return NULL; 1016 } 1017 buffer_string_set_length(c->mem, dlen); 1018 1019 chunkqueue_release_chunks(cq); 1020 chunkqueue_append_chunk(cq, c); 1021 return c->mem; 1022 } 1023