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 int chunks_oversized_n; 32 static const array *chunkqueue_default_tempdirs = NULL; 33 static off_t chunkqueue_default_tempfile_size = DEFAULT_TEMPFILE_SIZE; 34 35 void chunkqueue_set_chunk_size (size_t sz) 36 { 37 size_t x = 1024; 38 while (x < sz && x < (1u << 30)) x <<= 1; 39 chunk_buf_sz = sz > 0 ? x : 8192; 40 } 41 42 void chunkqueue_set_tempdirs_default_reset (void) 43 { 44 chunk_buf_sz = 8192; 45 chunkqueue_default_tempdirs = NULL; 46 chunkqueue_default_tempfile_size = DEFAULT_TEMPFILE_SIZE; 47 } 48 49 /* chunk buffer (c->mem) is never NULL; specialize routines from buffer.h */ 50 __attribute_pure__ 51 static inline size_t chunk_buffer_string_length(const buffer *b) { 52 return 0 != b->used ? b->used - 1 : 0; 53 } 54 __attribute_pure__ 55 static inline int chunk_buffer_string_is_empty(const buffer *b) { 56 return b->used < 2; 57 } 58 __attribute_pure__ 59 static inline size_t chunk_buffer_string_space(const buffer *b) { 60 return b->size ? b->size - (b->used | (0 == b->used)) : 0; 61 } 62 63 chunkqueue *chunkqueue_init(chunkqueue *cq) { 64 /* (if caller passes non-NULL cq, it must be 0-init) */ 65 if (NULL == cq) { 66 cq = calloc(1, sizeof(*cq)); 67 force_assert(NULL != cq); 68 } 69 70 cq->first = NULL; 71 cq->last = NULL; 72 73 cq->tempdirs = chunkqueue_default_tempdirs; 74 cq->upload_temp_file_size = chunkqueue_default_tempfile_size; 75 76 return cq; 77 } 78 79 __attribute_returns_nonnull__ 80 static chunk *chunk_init(size_t sz) { 81 chunk * const restrict c = calloc(1, sizeof(*c)); 82 force_assert(NULL != c); 83 84 #if 0 /*(zeroed by calloc())*/ 85 c->type = MEM_CHUNK; 86 c->next = NULL; 87 c->offset = 0; 88 c->file.length = 0; 89 c->file.mmap.length = c->file.mmap.offset = 0; 90 c->file.is_temp = 0; 91 #endif 92 c->file.fd = -1; 93 c->file.mmap.start = MAP_FAILED; 94 95 c->mem = buffer_init(); 96 buffer_string_prepare_copy(c->mem, sz-1); 97 98 return c; 99 } 100 101 static void chunk_reset_file_chunk(chunk *c) { 102 if (c->file.is_temp) { 103 c->file.is_temp = 0; 104 if (!chunk_buffer_string_is_empty(c->mem)) 105 unlink(c->mem->ptr); 106 } 107 if (c->file.refchg) { 108 c->file.refchg(c->file.ref, -1); 109 c->file.refchg = 0; /* NULL fn ptr */ 110 c->file.ref = NULL; 111 } 112 else if (c->file.fd != -1) { 113 close(c->file.fd); 114 } 115 if (MAP_FAILED != c->file.mmap.start) { 116 munmap(c->file.mmap.start, c->file.mmap.length); 117 c->file.mmap.start = MAP_FAILED; 118 c->file.mmap.length = c->file.mmap.offset = 0; 119 } 120 c->file.fd = -1; 121 c->file.length = 0; 122 c->type = MEM_CHUNK; 123 } 124 125 static void chunk_reset(chunk *c) { 126 if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c); 127 128 buffer_clear(c->mem); 129 c->offset = 0; 130 } 131 132 static void chunk_free(chunk *c) { 133 if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c); 134 buffer_free(c->mem); 135 free(c); 136 } 137 138 static chunk * chunk_pop_oversized(size_t sz) { 139 /* future: might have buckets of certain sizes, up to socket buf sizes */ 140 if (chunks_oversized && chunks_oversized->mem->size >= sz) { 141 --chunks_oversized_n; 142 chunk *c = chunks_oversized; 143 chunks_oversized = c->next; 144 return c; 145 } 146 return NULL; 147 } 148 149 static void chunk_push_oversized(chunk * const c, const size_t sz) { 150 if (chunks_oversized_n < 64 && chunk_buf_sz >= 4096) { 151 ++chunks_oversized_n; 152 chunk **co = &chunks_oversized; 153 while (*co && sz < (*co)->mem->size) co = &(*co)->next; 154 c->next = *co; 155 *co = c; 156 } 157 else 158 chunk_free(c); 159 } 160 161 __attribute_returns_nonnull__ 162 static buffer * chunk_buffer_acquire_sz(size_t sz) { 163 chunk *c; 164 buffer *b; 165 if (sz <= chunk_buf_sz) { 166 if (chunks) { 167 c = chunks; 168 chunks = c->next; 169 } 170 else 171 c = chunk_init(chunk_buf_sz); 172 /* future: might choose to pop from chunks_oversized, if available 173 * (even if larger than sz) rather than allocating new chunk 174 * (and if doing so, might replace chunks_oversized_n) */ 175 } 176 else { 177 /*(round up to nearest chunk_buf_sz)*/ 178 sz = (sz + (chunk_buf_sz-1)) & ~(chunk_buf_sz-1); 179 c = chunk_pop_oversized(sz); 180 if (NULL == c) 181 c = chunk_init(sz); 182 } 183 c->next = chunk_buffers; 184 chunk_buffers = c; 185 b = c->mem; 186 c->mem = NULL; 187 return b; 188 } 189 190 buffer * chunk_buffer_acquire(void) { 191 return chunk_buffer_acquire_sz(chunk_buf_sz); 192 } 193 194 void chunk_buffer_release(buffer *b) { 195 if (NULL == b) return; 196 if (chunk_buffers) { 197 chunk *c = chunk_buffers; 198 chunk_buffers = c->next; 199 c->mem = b; 200 buffer_clear(b); 201 if (b->size == chunk_buf_sz) { 202 c->next = chunks; 203 chunks = c; 204 } 205 else if (b->size > chunk_buf_sz) 206 chunk_push_oversized(c, b->size); 207 else 208 chunk_free(c); 209 } 210 else { 211 buffer_free(b); 212 } 213 } 214 215 size_t chunk_buffer_prepare_append(buffer * const b, size_t sz) { 216 if (sz > chunk_buffer_string_space(b)) { 217 sz += b->used ? b->used : 1; 218 buffer * const cb = chunk_buffer_acquire_sz(sz); 219 /* swap buffer contents and copy original b->ptr into larger b->ptr */ 220 /*(this does more than buffer_move())*/ 221 buffer tb = *b; 222 *b = *cb; 223 *cb = tb; 224 if ((b->used = tb.used)) 225 memcpy(b->ptr, tb.ptr, tb.used); 226 chunk_buffer_release(cb); 227 } 228 return chunk_buffer_string_space(b); 229 } 230 231 __attribute_returns_nonnull__ 232 static chunk * chunk_acquire(size_t sz) { 233 if (sz <= chunk_buf_sz) { 234 if (chunks) { 235 chunk *c = chunks; 236 chunks = c->next; 237 return c; 238 } 239 sz = chunk_buf_sz; 240 } 241 else { 242 /*(round up to nearest chunk_buf_sz)*/ 243 sz = (sz + (chunk_buf_sz-1)) & ~(chunk_buf_sz-1); 244 chunk *c = chunk_pop_oversized(sz); 245 if (c) return c; 246 } 247 248 return chunk_init(sz); 249 } 250 251 static void chunk_release(chunk *c) { 252 const size_t sz = c->mem->size; 253 if (sz == chunk_buf_sz) { 254 chunk_reset(c); 255 c->next = chunks; 256 chunks = c; 257 } 258 else if (sz > chunk_buf_sz) { 259 chunk_reset(c); 260 chunk_push_oversized(c, sz); 261 } 262 else { 263 chunk_free(c); 264 } 265 } 266 267 void chunkqueue_chunk_pool_clear(void) 268 { 269 for (chunk *next, *c = chunks; c; c = next) { 270 next = c->next; 271 chunk_free(c); 272 } 273 chunks = NULL; 274 for (chunk *next, *c = chunks_oversized; c; c = next) { 275 next = c->next; 276 chunk_free(c); 277 } 278 chunks_oversized = NULL; 279 chunks_oversized_n = 0; 280 } 281 282 void chunkqueue_chunk_pool_free(void) 283 { 284 chunkqueue_chunk_pool_clear(); 285 for (chunk *next, *c = chunk_buffers; c; c = next) { 286 next = c->next; 287 c->mem = buffer_init(); /*(chunk_reset() expects c->mem != NULL)*/ 288 chunk_free(c); 289 } 290 chunk_buffers = NULL; 291 } 292 293 __attribute_pure__ 294 static off_t chunk_remaining_length(const chunk *c) { 295 /* MEM_CHUNK or FILE_CHUNK */ 296 return (c->type == MEM_CHUNK 297 ? (off_t)chunk_buffer_string_length(c->mem) 298 : c->file.length) 299 - c->offset; 300 } 301 302 static void chunkqueue_release_chunks(chunkqueue *cq) { 303 cq->last = NULL; 304 for (chunk *c; (c = cq->first); ) { 305 cq->first = c->next; 306 chunk_release(c); 307 } 308 } 309 310 void chunkqueue_free(chunkqueue *cq) { 311 if (NULL == cq) return; 312 chunkqueue_release_chunks(cq); 313 free(cq); 314 } 315 316 static void chunkqueue_prepend_chunk(chunkqueue * const restrict cq, chunk * const restrict c) { 317 if (NULL == (c->next = cq->first)) cq->last = c; 318 cq->first = c; 319 } 320 321 static void chunkqueue_append_chunk(chunkqueue * const restrict cq, chunk * const restrict c) { 322 c->next = NULL; 323 *(cq->last ? &cq->last->next : &cq->first) = c; 324 cq->last = c; 325 } 326 327 __attribute_returns_nonnull__ 328 static chunk * chunkqueue_prepend_mem_chunk(chunkqueue *cq, size_t sz) { 329 chunk *c = chunk_acquire(sz); 330 chunkqueue_prepend_chunk(cq, c); 331 return c; 332 } 333 334 __attribute_returns_nonnull__ 335 static chunk * chunkqueue_append_mem_chunk(chunkqueue *cq, size_t sz) { 336 chunk *c = chunk_acquire(sz); 337 chunkqueue_append_chunk(cq, c); 338 return c; 339 } 340 341 __attribute_returns_nonnull__ 342 static chunk * chunkqueue_append_file_chunk(chunkqueue * const restrict cq, const buffer * const restrict fn, off_t offset, off_t len) { 343 chunk *c = chunk_acquire(buffer_string_length(fn)+1); 344 chunkqueue_append_chunk(cq, c); 345 c->type = FILE_CHUNK; 346 c->offset = offset; 347 c->file.length = offset + len; 348 cq->bytes_in += len; 349 buffer_copy_buffer(c->mem, fn); 350 return c; 351 } 352 353 void chunkqueue_reset(chunkqueue *cq) { 354 chunkqueue_release_chunks(cq); 355 cq->bytes_in = 0; 356 cq->bytes_out = 0; 357 cq->tempdir_idx = 0; 358 } 359 360 void chunkqueue_append_file_fd(chunkqueue * const restrict cq, const buffer * const restrict fn, int fd, off_t offset, off_t len) { 361 if (len > 0) { 362 (chunkqueue_append_file_chunk(cq, fn, offset, len))->file.fd = fd; 363 } 364 else { 365 close(fd); 366 } 367 } 368 369 void chunkqueue_append_file(chunkqueue * const restrict cq, const buffer * const restrict fn, off_t offset, off_t len) { 370 if (len > 0) { 371 chunkqueue_append_file_chunk(cq, fn, offset, len); 372 } 373 } 374 375 376 static int chunkqueue_append_mem_extend_chunk(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) { 377 chunk *c = cq->last; 378 if (0 == len) return 1; 379 if (c != NULL && c->type == MEM_CHUNK 380 && chunk_buffer_string_space(c->mem) >= len) { 381 buffer_append_string_len(c->mem, mem, len); 382 cq->bytes_in += len; 383 return 1; 384 } 385 return 0; 386 } 387 388 389 void chunkqueue_append_buffer(chunkqueue * const restrict cq, buffer * const restrict mem) { 390 chunk *c; 391 size_t len = buffer_string_length(mem); 392 if (len < 256 && chunkqueue_append_mem_extend_chunk(cq, mem->ptr, len)) return; 393 394 c = chunkqueue_append_mem_chunk(cq, chunk_buf_sz); 395 cq->bytes_in += len; 396 buffer_move(c->mem, mem); 397 } 398 399 400 void chunkqueue_append_mem(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) { 401 chunk *c; 402 if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len)) 403 return; 404 405 c = chunkqueue_append_mem_chunk(cq, len+1); 406 cq->bytes_in += len; 407 buffer_copy_string_len(c->mem, mem, len); 408 } 409 410 411 void chunkqueue_append_mem_min(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) { 412 chunk *c; 413 if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len)) 414 return; 415 416 c = chunk_init(len+1); 417 chunkqueue_append_chunk(cq, c); 418 cq->bytes_in += len; 419 buffer_copy_string_len(c->mem, mem, len); 420 } 421 422 423 void chunkqueue_append_chunkqueue(chunkqueue * const restrict cq, chunkqueue * const restrict src) { 424 if (NULL == src->first) return; 425 426 if (NULL == cq->first) { 427 cq->first = src->first; 428 } else { 429 cq->last->next = src->first; 430 } 431 cq->last = src->last; 432 cq->bytes_in += chunkqueue_length(src); 433 434 src->first = NULL; 435 src->last = NULL; 436 src->bytes_out = src->bytes_in; 437 } 438 439 440 buffer * chunkqueue_prepend_buffer_open_sz(chunkqueue *cq, size_t sz) { 441 chunk * const c = chunkqueue_prepend_mem_chunk(cq, sz); 442 return c->mem; 443 } 444 445 446 buffer * chunkqueue_prepend_buffer_open(chunkqueue *cq) { 447 return chunkqueue_prepend_buffer_open_sz(cq, chunk_buf_sz); 448 } 449 450 451 void chunkqueue_prepend_buffer_commit(chunkqueue *cq) { 452 cq->bytes_in += chunk_buffer_string_length(cq->first->mem); 453 } 454 455 456 buffer * chunkqueue_append_buffer_open_sz(chunkqueue *cq, size_t sz) { 457 chunk * const c = chunkqueue_append_mem_chunk(cq, sz); 458 return c->mem; 459 } 460 461 462 buffer * chunkqueue_append_buffer_open(chunkqueue *cq) { 463 return chunkqueue_append_buffer_open_sz(cq, chunk_buf_sz); 464 } 465 466 467 void chunkqueue_append_buffer_commit(chunkqueue *cq) { 468 cq->bytes_in += chunk_buffer_string_length(cq->last->mem); 469 } 470 471 472 static void chunkqueue_remove_empty_chunks(chunkqueue *cq); 473 474 475 char * chunkqueue_get_memory(chunkqueue * const restrict cq, size_t * const restrict len) { 476 size_t sz = *len ? *len : (chunk_buf_sz >> 1); 477 buffer *b; 478 chunk *c = cq->last; 479 if (NULL != c && MEM_CHUNK == c->type) { 480 /* return pointer into existing buffer if large enough */ 481 size_t avail = chunk_buffer_string_space(c->mem); 482 if (avail >= sz) { 483 *len = avail; 484 b = c->mem; 485 return b->ptr + chunk_buffer_string_length(b); 486 } 487 } 488 489 /* allocate new chunk */ 490 b = chunkqueue_append_buffer_open_sz(cq, sz); 491 *len = chunk_buffer_string_space(b); 492 return b->ptr; 493 } 494 495 void chunkqueue_use_memory(chunkqueue * const restrict cq, chunk *ckpt, size_t len) { 496 buffer *b = cq->last->mem; 497 498 if (len > 0) { 499 buffer_commit(b, len); 500 cq->bytes_in += len; 501 if (cq->last == ckpt || NULL == ckpt || MEM_CHUNK != ckpt->type 502 || len > chunk_buffer_string_space(ckpt->mem)) return; 503 504 buffer_append_string_buffer(ckpt->mem, b); 505 } 506 else if (!chunk_buffer_string_is_empty(b)) { /*(cq->last == ckpt)*/ 507 return; /* last chunk is not empty */ 508 } 509 510 /* remove empty last chunk */ 511 chunk_release(cq->last); 512 cq->last = ckpt; 513 *(ckpt ? &ckpt->next : &cq->first) = NULL; 514 } 515 516 void chunkqueue_update_file(chunkqueue * const restrict cq, chunk *c, off_t len) { 517 /*assert(c->type == FILE_CHUNK);*/ 518 c->file.length += len; 519 cq->bytes_in += len; 520 } 521 522 void chunkqueue_set_tempdirs_default (const array *tempdirs, off_t upload_temp_file_size) { 523 chunkqueue_default_tempdirs = tempdirs; 524 chunkqueue_default_tempfile_size 525 = (0 == upload_temp_file_size) ? DEFAULT_TEMPFILE_SIZE 526 : (upload_temp_file_size > MAX_TEMPFILE_SIZE) ? MAX_TEMPFILE_SIZE 527 : upload_temp_file_size; 528 } 529 530 void chunkqueue_set_tempdirs(chunkqueue * const restrict cq, const array * const restrict tempdirs, off_t upload_temp_file_size) { 531 force_assert(NULL != cq); 532 cq->tempdirs = tempdirs; 533 cq->upload_temp_file_size 534 = (0 == upload_temp_file_size) ? DEFAULT_TEMPFILE_SIZE 535 : (upload_temp_file_size > MAX_TEMPFILE_SIZE) ? MAX_TEMPFILE_SIZE 536 : upload_temp_file_size; 537 cq->tempdir_idx = 0; 538 } 539 540 static void chunkqueue_dup_file_chunk_fd (chunk * const restrict d, const chunk * const restrict c) { 541 /*assert(d != c);*/ 542 /*assert(d->type == FILE_CHUNK);*/ 543 /*assert(c->type == FILE_CHUNK);*/ 544 if (c->file.fd >= 0) { 545 if (c->file.refchg) { 546 d->file.fd = c->file.fd; 547 d->file.ref = c->file.ref; 548 d->file.refchg = c->file.refchg; 549 d->file.refchg(d->file.ref, 1); 550 } 551 else 552 d->file.fd = fdevent_dup_cloexec(c->file.fd); 553 } 554 } 555 556 static void chunkqueue_steal_partial_file_chunk(chunkqueue * const restrict dest, const chunk * const restrict c, const off_t len) { 557 chunkqueue_append_file(dest, c->mem, c->offset, len); 558 chunkqueue_dup_file_chunk_fd(dest->last, c); 559 } 560 561 void chunkqueue_steal(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len) { 562 while (len > 0) { 563 chunk *c = src->first; 564 off_t clen = 0, use; 565 566 if (NULL == c) break; 567 568 clen = chunk_remaining_length(c); 569 if (0 == clen) { 570 /* drop empty chunk */ 571 src->first = c->next; 572 if (c == src->last) src->last = NULL; 573 chunk_release(c); 574 continue; 575 } 576 577 use = len >= clen ? clen : len; 578 len -= use; 579 580 if (use == clen) { 581 /* move complete chunk */ 582 src->first = c->next; 583 if (c == src->last) src->last = NULL; 584 585 chunkqueue_append_chunk(dest, c); 586 dest->bytes_in += use; 587 } else { 588 /* partial chunk with length "use" */ 589 590 switch (c->type) { 591 case MEM_CHUNK: 592 chunkqueue_append_mem(dest, c->mem->ptr + c->offset, use); 593 break; 594 case FILE_CHUNK: 595 /* tempfile flag is in "last" chunk after the split */ 596 chunkqueue_steal_partial_file_chunk(dest, c, use); 597 break; 598 } 599 600 c->offset += use; 601 force_assert(0 == len); 602 } 603 604 src->bytes_out += use; 605 } 606 } 607 608 static chunk *chunkqueue_get_append_tempfile(chunkqueue * const restrict cq, log_error_st * const restrict errh) { 609 chunk *c; 610 buffer *template = buffer_init_string("/var/tmp/lighttpd-upload-XXXXXX"); 611 int fd = -1; 612 613 if (cq->tempdirs && cq->tempdirs->used) { 614 /* we have several tempdirs, only if all of them fail we jump out */ 615 616 for (errno = EIO; cq->tempdir_idx < cq->tempdirs->used; ++cq->tempdir_idx) { 617 data_string *ds = (data_string *)cq->tempdirs->data[cq->tempdir_idx]; 618 buffer_copy_path_len2(template, 619 CONST_BUF_LEN(&ds->value), 620 CONST_STR_LEN("lighttpd-upload-XXXXXX")); 621 if (-1 != (fd = fdevent_mkstemp_append(template->ptr))) break; 622 } 623 } else { 624 fd = fdevent_mkstemp_append(template->ptr); 625 } 626 627 if (fd < 0) { 628 /* (report only the last error to mkstemp() 629 * if multiple temp dirs attempted) */ 630 log_perror(errh, __FILE__, __LINE__, 631 "opening temp-file failed: %s", template->ptr); 632 buffer_free(template); 633 return NULL; 634 } 635 636 c = chunkqueue_append_file_chunk(cq, template, 0, 0); 637 c->file.fd = fd; 638 c->file.is_temp = 1; 639 640 buffer_free(template); 641 642 return c; 643 } 644 645 int chunkqueue_append_mem_to_tempfile(chunkqueue * const restrict dest, const char * restrict mem, size_t len, log_error_st * const restrict errh) { 646 chunk *dst_c; 647 ssize_t written; 648 649 do { 650 /* 651 * if the last chunk is 652 * - smaller than dest->upload_temp_file_size 653 * - not read yet (offset == 0) 654 * -> append to it (so it might actually become larger than dest->upload_temp_file_size) 655 * otherwise 656 * -> create a new chunk 657 * 658 * */ 659 660 dst_c = dest->last; 661 if (NULL != dst_c 662 && FILE_CHUNK == dst_c->type 663 && dst_c->file.is_temp 664 && dst_c->file.fd >= 0 665 && 0 == dst_c->offset) { 666 /* ok, take the last chunk for our job */ 667 668 if (dst_c->file.length >= (off_t)dest->upload_temp_file_size) { 669 /* the chunk is too large now, close it */ 670 force_assert(0 == dst_c->file.refchg); /*(else should not happen)*/ 671 int rc = close(dst_c->file.fd); 672 dst_c->file.fd = -1; 673 if (0 != rc) { 674 log_perror(errh, __FILE__, __LINE__, 675 "close() temp-file %s failed", dst_c->mem->ptr); 676 return -1; 677 } 678 dst_c = NULL; 679 } 680 } else { 681 dst_c = NULL; 682 } 683 684 if (NULL == dst_c && NULL == (dst_c = chunkqueue_get_append_tempfile(dest, errh))) { 685 return -1; 686 } 687 #ifdef __COVERITY__ 688 if (dst_c->file.fd < 0) return -1; 689 #endif 690 691 /* (dst_c->file.fd >= 0) */ 692 /* coverity[negative_returns : FALSE] */ 693 written = write(dst_c->file.fd, mem, len); 694 695 if ((size_t) written == len) { 696 dst_c->file.length += len; 697 dest->bytes_in += len; 698 699 return 0; 700 } else if (written >= 0) { 701 /*(assume EINTR if partial write and retry write(); 702 * retry write() might fail with ENOSPC if no more space on volume)*/ 703 dest->bytes_in += written; 704 mem += written; 705 len -= (size_t)written; 706 dst_c->file.length += (size_t)written; 707 /* continue; retry */ 708 } else if (errno == EINTR) { 709 /* continue; retry */ 710 } else { 711 int retry = (errno == ENOSPC && dest->tempdirs && ++dest->tempdir_idx < dest->tempdirs->used); 712 if (!retry) { 713 log_perror(errh, __FILE__, __LINE__, 714 "write() temp-file %s failed", dst_c->mem->ptr); 715 } 716 717 if (0 == chunk_remaining_length(dst_c)) { 718 /*(remove empty chunk and unlink tempfile)*/ 719 chunkqueue_remove_empty_chunks(dest); 720 } else {/*(close tempfile; avoid later attempts to append)*/ 721 force_assert(0 == dst_c->file.refchg); /*(else should not happen)*/ 722 int rc = close(dst_c->file.fd); 723 dst_c->file.fd = -1; 724 if (0 != rc) { 725 log_perror(errh, __FILE__, __LINE__, 726 "close() temp-file %s failed", dst_c->mem->ptr); 727 return -1; 728 } 729 } 730 if (!retry) break; /* return -1; */ 731 732 /* continue; retry */ 733 } 734 735 } while (dst_c); 736 737 return -1; 738 } 739 740 int chunkqueue_steal_with_tempfiles(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len, log_error_st * const restrict errh) { 741 while (len > 0) { 742 chunk *c = src->first; 743 off_t clen = 0, use; 744 745 if (NULL == c) break; 746 747 clen = chunk_remaining_length(c); 748 if (0 == clen) { 749 /* drop empty chunk */ 750 src->first = c->next; 751 if (c == src->last) src->last = NULL; 752 chunk_release(c); 753 continue; 754 } 755 756 use = (len >= clen) ? clen : len; 757 len -= use; 758 759 switch (c->type) { 760 case FILE_CHUNK: 761 if (use == clen) { 762 /* move complete chunk */ 763 src->first = c->next; 764 if (c == src->last) src->last = NULL; 765 chunkqueue_append_chunk(dest, c); 766 dest->bytes_in += use; 767 } else { 768 /* partial chunk with length "use" */ 769 /* tempfile flag is in "last" chunk after the split */ 770 chunkqueue_steal_partial_file_chunk(dest, c, use); 771 c->offset += use; 772 force_assert(0 == len); 773 } 774 break; 775 776 case MEM_CHUNK: 777 /* store "use" bytes from memory chunk in tempfile */ 778 if (0 != chunkqueue_append_mem_to_tempfile(dest, c->mem->ptr + c->offset, use, errh)) { 779 return -1; 780 } 781 782 if (use == clen) { 783 /* finished chunk */ 784 src->first = c->next; 785 if (c == src->last) src->last = NULL; 786 chunk_release(c); 787 } else { 788 /* partial chunk */ 789 c->offset += use; 790 force_assert(0 == len); 791 } 792 break; 793 } 794 795 src->bytes_out += use; 796 } 797 798 return 0; 799 } 800 801 void chunkqueue_append_cq_range (chunkqueue * const dst, const chunkqueue * const src, off_t offset, off_t len) { 802 /* similar to chunkqueue_steal() but copy and append src range to dst cq */ 803 /* (dst cq and src cq can be the same cq, so neither is marked restrict) */ 804 805 /* copy and append range len from src to dst */ 806 for (const chunk *c = src->first; len > 0 && c != NULL; c = c->next) { 807 /* scan into src to range offset (also skips empty chunks) */ 808 off_t clen = chunk_remaining_length(c); 809 if (offset >= clen) { 810 offset -= clen; 811 continue; 812 } 813 clen -= offset; 814 if (len < clen) clen = len; 815 len -= clen; 816 817 if (c->type == FILE_CHUNK) { 818 chunkqueue_append_file(dst, c->mem, c->offset + offset, clen); 819 chunkqueue_dup_file_chunk_fd(dst->last, c); 820 } 821 else { /*(c->type == MEM_CHUNK)*/ 822 /*(string refs would reduce copying, 823 * but this path is not expected to be hot)*/ 824 chunkqueue_append_mem(dst, c->mem->ptr + c->offset + offset, clen); 825 } 826 offset = 0; 827 } 828 } 829 830 void chunkqueue_mark_written(chunkqueue *cq, off_t len) { 831 cq->bytes_out += len; 832 833 for (chunk *c; (c = cq->first); ) { 834 off_t c_len = chunk_remaining_length(c); 835 if (len >= c_len) { /* chunk got finished */ 836 len -= c_len; 837 cq->first = c->next; 838 chunk_release(c); 839 if (0 == len) break; 840 } 841 else { /* partial chunk */ 842 c->offset += len; 843 return; /* chunk not finished */ 844 } 845 } 846 847 if (NULL == cq->first) 848 cq->last = NULL; 849 else 850 chunkqueue_remove_finished_chunks(cq); 851 } 852 853 void chunkqueue_remove_finished_chunks(chunkqueue *cq) { 854 for (chunk *c; (c = cq->first) && 0 == chunk_remaining_length(c); ){ 855 if (NULL == (cq->first = c->next)) cq->last = NULL; 856 chunk_release(c); 857 } 858 } 859 860 static void chunkqueue_remove_empty_chunks(chunkqueue *cq) { 861 chunk *c; 862 chunkqueue_remove_finished_chunks(cq); 863 864 for (c = cq->first; c && c->next; c = c->next) { 865 if (0 == chunk_remaining_length(c->next)) { 866 chunk *empty = c->next; 867 c->next = empty->next; 868 if (empty == cq->last) cq->last = c; 869 chunk_release(empty); 870 } 871 } 872 } 873 874 void chunkqueue_compact_mem_offset(chunkqueue * const cq) { 875 chunk * const restrict c = cq->first; 876 if (0 == c->offset) return; 877 if (c->type != MEM_CHUNK) return; /*(should not happen)*/ 878 879 buffer * const restrict b = c->mem; 880 size_t len = chunk_buffer_string_length(b) - c->offset; 881 memmove(b->ptr, b->ptr+c->offset, len); 882 c->offset = 0; 883 buffer_string_set_length(b, len); 884 } 885 886 void chunkqueue_compact_mem(chunkqueue *cq, size_t clen) { 887 /* caller must guarantee that chunks in chunkqueue are MEM_CHUNK, 888 * which is currently always true when reading input from client */ 889 chunk *c = cq->first; 890 buffer *b = c->mem; 891 size_t len = chunk_buffer_string_length(b) - c->offset; 892 if (len >= clen) return; 893 if (b->size > clen) { 894 if (chunk_buffer_string_space(b) < clen - len) 895 chunkqueue_compact_mem_offset(cq); 896 } 897 else { 898 b = chunkqueue_prepend_buffer_open_sz(cq, clen+1); 899 buffer_append_string_len(b, c->mem->ptr + c->offset, len); 900 cq->first->next = c->next; 901 if (NULL == c->next) cq->last = cq->first; 902 chunk_release(c); 903 c = cq->first; 904 } 905 906 for (chunk *fc = c; ((clen -= len) && (c = fc->next)); ) { 907 len = chunk_buffer_string_length(c->mem) - c->offset; 908 if (len > clen) { 909 buffer_append_string_len(b, c->mem->ptr + c->offset, clen); 910 c->offset += clen; 911 break; 912 } 913 buffer_append_string_len(b, c->mem->ptr + c->offset, len); 914 fc->next = c->next; 915 if (NULL == c->next) cq->last = fc; 916 chunk_release(c); 917 } 918 /* chunkqueue_prepend_buffer_commit() is not called here; 919 * no data added/removed from chunkqueue; consolidated only */ 920 } 921 922 static int chunk_open_file_chunk(chunk * const restrict c, log_error_st * const restrict errh) { 923 if (-1 == c->file.fd) { 924 /* (permit symlinks; should already have been checked. However, TOC-TOU remains) */ 925 if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, 1, O_RDONLY, 0))) { 926 log_perror(errh, __FILE__, __LINE__, "open failed: %s",c->mem->ptr); 927 return -1; 928 } 929 } 930 931 /*(skip file size checks if file is temp file created by lighttpd)*/ 932 if (c->file.is_temp) return 0; 933 934 force_assert(FILE_CHUNK == c->type); 935 force_assert(c->offset >= 0 && c->offset <= c->file.length); 936 937 struct stat st; 938 if (-1 == fstat(c->file.fd, &st)) { 939 log_perror(errh, __FILE__, __LINE__, "fstat failed"); 940 return -1; 941 } 942 943 const off_t offset = c->offset; 944 const off_t len = c->file.length - c->offset; 945 if (offset > st.st_size || len > st.st_size || offset > st.st_size - len) { 946 log_error(errh, __FILE__, __LINE__, "file shrunk: %s", c->mem->ptr); 947 return -1; 948 } 949 950 return 0; 951 } 952 953 int chunkqueue_open_file_chunk(chunkqueue * const restrict cq, log_error_st * const restrict errh) { 954 return chunk_open_file_chunk(cq->first, errh); 955 } 956 957 958 #if defined(HAVE_MMAP) || defined(_WIN32) /*(see local sys-mmap.h)*/ 959 __attribute_cold__ 960 #endif 961 __attribute_noinline__ 962 static ssize_t 963 chunkqueue_write_chunk_file_intermed (const int fd, chunk * const restrict c, log_error_st * const errh) 964 { 965 char buf[16384]; 966 char *data = buf; 967 const off_t count = c->file.length - c->offset; 968 uint32_t dlen = count < (off_t)sizeof(buf) ? (uint32_t)count : sizeof(buf); 969 chunkqueue cq = {c,c,0,0,0,0,0}; /*(fake cq for chunkqueue_peek_data())*/ 970 if (0 != chunkqueue_peek_data(&cq, &data, &dlen, errh) && 0 == dlen) 971 return -1; 972 ssize_t wr; 973 do { wr = write(fd, data, dlen); } while (-1 == wr && errno == EINTR); 974 return wr; 975 } 976 977 978 #if defined(HAVE_MMAP) || defined(_WIN32) /*(see local sys-mmap.h)*/ 979 /*(improved from network_write_mmap.c)*/ 980 static off_t 981 mmap_align_offset (off_t start) 982 { 983 static off_t pagemask = 0; 984 if (0 == pagemask) { 985 #ifndef _WIN32 986 long pagesize = sysconf(_SC_PAGESIZE); 987 #else 988 long pagesize = -1; /*(not implemented (yet))*/ 989 #endif 990 if (-1 == pagesize) pagesize = 4096; 991 pagemask = ~((off_t)pagesize - 1); /* pagesize always power-of-2 */ 992 } 993 return (start & pagemask); 994 } 995 #endif 996 997 998 #if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE \ 999 && (!defined _LARGEFILE_SOURCE || defined HAVE_SENDFILE64) \ 1000 && defined(__linux__) && !defined HAVE_SENDFILE_BROKEN 1001 #include <sys/sendfile.h> 1002 #include <stdint.h> 1003 #endif 1004 static ssize_t 1005 chunkqueue_write_chunk_file (const int fd, chunk * const restrict c, log_error_st * const errh) 1006 { 1007 /*(similar to network_write_file_chunk_mmap(), but does not use send() on 1008 * Windows because fd is expected to be file or pipe here, not socket)*/ 1009 1010 if (0 != chunk_open_file_chunk(c, errh)) 1011 return -1; 1012 1013 const off_t count = c->file.length - c->offset; 1014 if (0 == count) return 0; /*(sanity check)*/ 1015 1016 ssize_t wr; 1017 #if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE \ 1018 && (!defined _LARGEFILE_SOURCE || defined HAVE_SENDFILE64) \ 1019 && defined(__linux__) && !defined HAVE_SENDFILE_BROKEN 1020 /* Linux kernel >= 2.6.33 supports sendfile() between most fd types */ 1021 off_t offset = c->offset; 1022 wr = sendfile(fd, c->file.fd, &offset, count<INT32_MAX ? count : INT32_MAX); 1023 if (wr >= 0) return wr; 1024 1025 if (wr < 0 && (errno == EINVAL || errno == ENOSYS)) 1026 #endif 1027 { 1028 #if defined(HAVE_MMAP) || defined(_WIN32) /*(see local sys-mmap.h)*/ 1029 /*(caller is responsible for handling SIGBUS if chunkqueue might contain 1030 * untrusted file, i.e. any file other than lighttpd-created tempfile)*/ 1031 /*(tempfiles are expected for input, MAP_PRIVATE used for portability)*/ 1032 /*(mmaps and writes complete chunk instead of only small parts; files 1033 * are expected to be temp files with reasonable chunk sizes)*/ 1034 1035 /* (re)mmap the buffer if range is not covered completely */ 1036 if (MAP_FAILED == c->file.mmap.start 1037 || c->offset < c->file.mmap.offset 1038 || c->file.length 1039 > (off_t)(c->file.mmap.offset + c->file.mmap.length)) { 1040 1041 if (MAP_FAILED != c->file.mmap.start) { 1042 munmap(c->file.mmap.start, c->file.mmap.length); 1043 c->file.mmap.start = MAP_FAILED; 1044 } 1045 1046 c->file.mmap.offset = mmap_align_offset(c->offset); 1047 c->file.mmap.length = c->file.length - c->file.mmap.offset; 1048 c->file.mmap.start = 1049 mmap(NULL, c->file.mmap.length, PROT_READ, MAP_PRIVATE, 1050 c->file.fd, c->file.mmap.offset); 1051 1052 #if 0 1053 /* close() fd as soon as fully mmap() rather than when done w/ chunk 1054 * (possibly worthwhile to keep active fd count lower) */ 1055 if (c->file.is_temp && !c->file.refchg) { 1056 close(c->file.fd); 1057 c->file.fd = -1; 1058 } 1059 #endif 1060 } 1061 1062 if (MAP_FAILED != c->file.mmap.start) { 1063 const char * const data = 1064 c->file.mmap.start + c->offset - c->file.mmap.offset; 1065 do { wr = write(fd,data,count); } while (-1 == wr && errno==EINTR); 1066 } 1067 else 1068 #endif 1069 wr = chunkqueue_write_chunk_file_intermed(fd, c, errh); 1070 } 1071 return wr; 1072 } 1073 1074 1075 static ssize_t 1076 chunkqueue_write_chunk_mem (const int fd, const chunk * const restrict c) 1077 { 1078 const void * const buf = c->mem->ptr + c->offset; 1079 const size_t count = chunk_buffer_string_length(c->mem) - (size_t)c->offset; 1080 ssize_t wr; 1081 do { wr = write(fd, buf, count); } while (-1 == wr && errno == EINTR); 1082 return wr; 1083 } 1084 1085 1086 ssize_t 1087 chunkqueue_write_chunk (const int fd, chunkqueue * const restrict cq, log_error_st * const restrict errh) 1088 { 1089 /*(note: expects non-empty cq->first)*/ 1090 chunk * const c = cq->first; 1091 switch (c->type) { 1092 case MEM_CHUNK: 1093 return chunkqueue_write_chunk_mem(fd, c); 1094 case FILE_CHUNK: 1095 return chunkqueue_write_chunk_file(fd, c, errh); 1096 default: 1097 errno = EINVAL; 1098 return -1; 1099 } 1100 } 1101 1102 1103 ssize_t 1104 chunkqueue_write_chunk_to_pipe (const int fd, chunkqueue * const restrict cq, log_error_st * const restrict errh) 1105 { 1106 /*(note: expects non-empty cq->first)*/ 1107 #ifdef SPLICE_F_NONBLOCK /* splice() temp files to pipe on Linux */ 1108 chunk * const c = cq->first; 1109 if (c->type == FILE_CHUNK) { 1110 loff_t abs_offset = c->offset; 1111 return (0 == chunk_open_file_chunk(c, errh)) 1112 ? splice(c->file.fd, &abs_offset, fd, NULL, 1113 (size_t)(c->file.length - c->offset), SPLICE_F_NONBLOCK) 1114 : -1; 1115 } 1116 #endif 1117 return chunkqueue_write_chunk(fd, cq, errh); 1118 } 1119 1120 1121 void 1122 chunkqueue_small_resp_optim (chunkqueue * const restrict cq) 1123 { 1124 /*(caller must verify response is small (and non-empty) before calling)*/ 1125 1126 /*(optimization to use fewer syscalls to send a small response by reading 1127 * small files into memory, thereby avoiding use of sendfile() and multiple 1128 * calls to writev() (benefit for cleartext (non-TLS) and <= HTTP/1.1)) 1129 *(If TLS, then will shortly need to be in memory for encryption anyway)*/ 1130 1131 /*assert(cq->first);*/ 1132 chunk *c = cq->first; 1133 chunk * const filec = c->next; 1134 if (c->type != MEM_CHUNK || filec != cq->last || filec->type != FILE_CHUNK) 1135 return; 1136 1137 const int fd = filec->file.fd; 1138 if (fd < 0) return; /*(require that file already be open)*/ 1139 off_t offset = filec->offset; 1140 if (-1 == lseek(fd, offset, SEEK_SET)) return; 1141 1142 /* Note: there should be no size change in chunkqueue, 1143 * so cq->bytes_in and cq->bytes_out should not be modified */ 1144 1145 buffer *b = c->mem; 1146 off_t len = filec->file.length - filec->offset; 1147 if ((size_t)len > chunk_buffer_string_space(b)) { 1148 chunk * const nc = chunk_acquire((size_t)len+1); 1149 c->next = nc; 1150 nc->next = filec; 1151 b = nc->mem; 1152 } 1153 1154 char * const ptr = b->ptr + chunk_buffer_string_length(b); 1155 ssize_t rd; 1156 offset = 0; /*(reuse offset var for offset into mem buffer)*/ 1157 do { 1158 rd = read(fd, ptr+offset, (size_t)len); 1159 } while (rd > 0 ? (offset += rd, len -= rd) : errno == EINTR); 1160 /*(contents of chunkqueue kept valid even if error reading from file)*/ 1161 if (len) 1162 cq->bytes_in -= len; 1163 buffer_commit(b, offset); 1164 filec->offset += offset; 1165 chunkqueue_remove_empty_chunks(cq); 1166 } 1167 1168 1169 int 1170 chunkqueue_peek_data (chunkqueue * const cq, 1171 char ** const data, uint32_t * const dlen, 1172 log_error_st * const errh) 1173 { 1174 char * const data_in = *data; 1175 const uint32_t data_insz = *dlen; 1176 *dlen = 0; 1177 1178 for (chunk *c = cq->first; c; ) { 1179 uint32_t space = data_insz - *dlen; 1180 switch (c->type) { 1181 case MEM_CHUNK: 1182 { 1183 uint32_t have = 1184 chunk_buffer_string_length(c->mem) - (uint32_t)c->offset; 1185 if (have > space) 1186 have = space; 1187 if (*dlen) 1188 memcpy(data_in + *dlen, c->mem->ptr + c->offset, have); 1189 else 1190 *data = c->mem->ptr + c->offset; /*(reference; defer copy)*/ 1191 *dlen += have; 1192 break; 1193 } 1194 1195 case FILE_CHUNK: 1196 if (c->file.fd >= 0 || 0 == chunk_open_file_chunk(c, errh)) { 1197 off_t offset = c->offset; 1198 off_t len = c->file.length - c->offset; 1199 if (len > (off_t)space) 1200 len = (off_t)space; 1201 1202 if (-1 == lseek(c->file.fd, offset, SEEK_SET)) { 1203 log_perror(errh, __FILE__, __LINE__, "lseek(\"%s\")", 1204 c->mem->ptr); 1205 return -1; 1206 } 1207 ssize_t rd; 1208 do { 1209 rd = read(c->file.fd, data_in + *dlen, (size_t)len); 1210 } while (-1 == rd && errno == EINTR); 1211 if (rd <= 0) { /* -1 error; 0 EOF (unexpected) */ 1212 log_perror(errh, __FILE__, __LINE__, "read(\"%s\")", 1213 c->mem->ptr); 1214 return -1; 1215 } 1216 1217 *dlen += (uint32_t)rd; 1218 break; 1219 } 1220 return -1; 1221 1222 default: 1223 return -1; 1224 } 1225 1226 if (*dlen == data_insz) 1227 break; 1228 1229 c = c->next; 1230 if (NULL == c) 1231 break; 1232 1233 if (*dlen && *data != data_in) { 1234 memcpy(data_in, *data, *dlen); 1235 *data = data_in; 1236 } 1237 } 1238 1239 return 0; 1240 } 1241 1242 1243 int 1244 chunkqueue_read_data (chunkqueue * const cq, 1245 char * const data, const uint32_t dlen, 1246 log_error_st * const errh) 1247 { 1248 char *ptr = data; 1249 uint32_t len = dlen; 1250 if (chunkqueue_peek_data(cq, &ptr, &len, errh) < 0 || len != dlen) 1251 return -1; 1252 if (data != ptr) memcpy(data, ptr, len); 1253 chunkqueue_mark_written(cq, len); 1254 return 0; 1255 } 1256 1257 1258 buffer * 1259 chunkqueue_read_squash (chunkqueue * const restrict cq, log_error_st * const restrict errh) 1260 { 1261 /* read and replace chunkqueue contents with single MEM_CHUNK. 1262 * cq->bytes_out is not modified */ 1263 1264 off_t cqlen = chunkqueue_length(cq); 1265 if (cqlen >= UINT32_MAX) return NULL; 1266 1267 if (cq->first && NULL == cq->first->next && cq->first->type == MEM_CHUNK) 1268 return cq->first->mem; 1269 1270 chunk * const c = chunk_acquire((uint32_t)cqlen+1); 1271 char *data = c->mem->ptr; 1272 uint32_t dlen = (uint32_t)cqlen; 1273 int rc = chunkqueue_peek_data(cq, &data, &dlen, errh); 1274 if (rc < 0) { 1275 chunk_release(c); 1276 return NULL; 1277 } 1278 buffer_string_set_length(c->mem, dlen); 1279 1280 chunkqueue_release_chunks(cq); 1281 chunkqueue_append_chunk(cq, c); 1282 return c->mem; 1283 } 1284