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 = 4096; 29 static chunk *chunks, *chunks_oversized; 30 static chunk *chunk_buffers; 31 static 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) : 4096; 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 chunkqueue *chunkqueue_init(void) { 46 chunkqueue *cq; 47 48 cq = calloc(1, sizeof(*cq)); 49 force_assert(NULL != cq); 50 51 cq->first = NULL; 52 cq->last = NULL; 53 54 cq->tempdirs = chunkqueue_default_tempdirs; 55 cq->upload_temp_file_size = chunkqueue_default_tempfile_size; 56 57 return cq; 58 } 59 60 static chunk *chunk_init(size_t sz) { 61 chunk *c; 62 63 c = calloc(1, sizeof(*c)); 64 force_assert(NULL != c); 65 66 c->type = MEM_CHUNK; 67 c->mem = buffer_init(); 68 c->file.start = c->file.length = c->file.mmap.offset = 0; 69 c->file.fd = -1; 70 c->file.mmap.start = MAP_FAILED; 71 c->file.mmap.length = 0; 72 c->file.is_temp = 0; 73 c->offset = 0; 74 c->next = NULL; 75 76 buffer_string_prepare_copy(c->mem, sz-1); 77 78 return c; 79 } 80 81 static void chunk_reset_file_chunk(chunk *c) { 82 if (c->file.is_temp && !buffer_string_is_empty(c->mem)) { 83 unlink(c->mem->ptr); 84 } 85 if (c->file.fd != -1) { 86 close(c->file.fd); 87 c->file.fd = -1; 88 } 89 if (MAP_FAILED != c->file.mmap.start) { 90 munmap(c->file.mmap.start, c->file.mmap.length); 91 c->file.mmap.start = MAP_FAILED; 92 } 93 c->file.start = c->file.length = c->file.mmap.offset = 0; 94 c->file.mmap.length = 0; 95 c->file.is_temp = 0; 96 c->type = MEM_CHUNK; 97 } 98 99 static void chunk_reset(chunk *c) { 100 if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c); 101 102 buffer_clear(c->mem); 103 c->offset = 0; 104 } 105 106 static void chunk_free(chunk *c) { 107 if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c); 108 buffer_free(c->mem); 109 free(c); 110 } 111 112 buffer * chunk_buffer_acquire(void) { 113 chunk *c; 114 buffer *b; 115 if (chunks) { 116 c = chunks; 117 chunks = c->next; 118 } 119 else { 120 c = chunk_init(chunk_buf_sz); 121 } 122 c->next = chunk_buffers; 123 chunk_buffers = c; 124 b = c->mem; 125 c->mem = NULL; 126 return b; 127 } 128 129 void chunk_buffer_release(buffer *b) { 130 if (NULL == b) return; 131 if (b->size >= chunk_buf_sz && chunk_buffers) { 132 chunk *c = chunk_buffers; 133 chunk_buffers = c->next; 134 c->mem = b; 135 c->next = chunks; 136 chunks = c; 137 buffer_clear(b); 138 } 139 else { 140 buffer_free(b); 141 } 142 } 143 144 static chunk * chunk_acquire(size_t sz) { 145 if (sz <= chunk_buf_sz) { 146 if (chunks) { 147 chunk *c = chunks; 148 chunks = c->next; 149 return c; 150 } 151 sz = chunk_buf_sz; 152 } 153 else { 154 sz = (sz + 8191) & ~8191uL; 155 /* future: might have buckets of certain sizes, up to socket buf sizes*/ 156 if (chunks_oversized && chunks_oversized->mem->size >= sz) { 157 chunk *c = chunks_oversized; 158 chunks_oversized = c->next; 159 return c; 160 } 161 } 162 163 return chunk_init(sz); 164 } 165 166 static void chunk_release(chunk *c) { 167 const size_t sz = c->mem->size; 168 if (sz == chunk_buf_sz) { 169 chunk_reset(c); 170 c->next = chunks; 171 chunks = c; 172 } 173 else if (sz > chunk_buf_sz) { 174 chunk_reset(c); 175 chunk **co = &chunks_oversized; 176 while (*co && sz < (*co)->mem->size) co = &(*co)->next; 177 c->next = *co; 178 *co = c; 179 } 180 else { 181 chunk_free(c); 182 } 183 } 184 185 void chunkqueue_chunk_pool_clear(void) 186 { 187 for (chunk *next, *c = chunks; c; c = next) { 188 next = c->next; 189 chunk_free(c); 190 } 191 chunks = NULL; 192 for (chunk *next, *c = chunks_oversized; c; c = next) { 193 next = c->next; 194 chunk_free(c); 195 } 196 chunks_oversized = NULL; 197 } 198 199 void chunkqueue_chunk_pool_free(void) 200 { 201 chunkqueue_chunk_pool_clear(); 202 for (chunk *next, *c = chunk_buffers; c; c = next) { 203 next = c->next; 204 c->mem = buffer_init(); /*(chunk_reset() expects c->mem != NULL)*/ 205 chunk_free(c); 206 } 207 chunk_buffers = NULL; 208 } 209 210 static off_t chunk_remaining_length(const chunk *c) { 211 off_t len = 0; 212 switch (c->type) { 213 case MEM_CHUNK: 214 len = buffer_string_length(c->mem); 215 break; 216 case FILE_CHUNK: 217 len = c->file.length; 218 break; 219 default: 220 force_assert(c->type == MEM_CHUNK || c->type == FILE_CHUNK); 221 break; 222 } 223 force_assert(c->offset <= len); 224 return len - c->offset; 225 } 226 227 void chunkqueue_free(chunkqueue *cq) { 228 chunk *c, *pc; 229 230 if (NULL == cq) return; 231 232 for (c = cq->first; c; ) { 233 pc = c; 234 c = c->next; 235 chunk_release(pc); 236 } 237 238 free(cq); 239 } 240 241 static void chunkqueue_prepend_chunk(chunkqueue *cq, chunk *c) { 242 c->next = cq->first; 243 cq->first = c; 244 245 if (NULL == cq->last) { 246 cq->last = c; 247 } 248 } 249 250 static void chunkqueue_append_chunk(chunkqueue *cq, chunk *c) { 251 c->next = NULL; 252 if (cq->last) { 253 cq->last->next = c; 254 } 255 cq->last = c; 256 257 if (NULL == cq->first) { 258 cq->first = c; 259 } 260 } 261 262 static chunk * chunkqueue_prepend_mem_chunk(chunkqueue *cq, size_t sz) { 263 chunk *c = chunk_acquire(sz); 264 chunkqueue_prepend_chunk(cq, c); 265 return c; 266 } 267 268 static chunk * chunkqueue_append_mem_chunk(chunkqueue *cq, size_t sz) { 269 chunk *c = chunk_acquire(sz); 270 chunkqueue_append_chunk(cq, c); 271 return c; 272 } 273 274 static chunk * chunkqueue_append_file_chunk(chunkqueue *cq, buffer *fn, off_t offset, off_t len) { 275 chunk *c = chunk_acquire(buffer_string_length(fn)+1); 276 chunkqueue_append_chunk(cq, c); 277 c->type = FILE_CHUNK; 278 c->file.start = offset; 279 c->file.length = len; 280 cq->bytes_in += len; 281 buffer_copy_buffer(c->mem, fn); 282 return c; 283 } 284 285 void chunkqueue_reset(chunkqueue *cq) { 286 chunk *cur = cq->first; 287 288 cq->first = cq->last = NULL; 289 290 while (NULL != cur) { 291 chunk *next = cur->next; 292 chunk_release(cur); 293 cur = next; 294 } 295 296 cq->bytes_in = 0; 297 cq->bytes_out = 0; 298 cq->tempdir_idx = 0; 299 } 300 301 void chunkqueue_append_file_fd(chunkqueue *cq, buffer *fn, int fd, off_t offset, off_t len) { 302 if (len > 0) { 303 (chunkqueue_append_file_chunk(cq, fn, offset, len))->file.fd = fd; 304 } 305 else { 306 close(fd); 307 } 308 } 309 310 void chunkqueue_append_file(chunkqueue *cq, buffer *fn, off_t offset, off_t len) { 311 if (len > 0) { 312 chunkqueue_append_file_chunk(cq, fn, offset, len); 313 } 314 } 315 316 317 static int chunkqueue_append_mem_extend_chunk(chunkqueue *cq, const char *mem, size_t len) { 318 chunk *c = cq->last; 319 if (0 == len) return 1; 320 if (c != NULL && c->type == MEM_CHUNK 321 && buffer_string_space(c->mem) >= len) { 322 buffer_append_string_len(c->mem, mem, len); 323 cq->bytes_in += len; 324 return 1; 325 } 326 return 0; 327 } 328 329 330 void chunkqueue_append_buffer(chunkqueue *cq, buffer *mem) { 331 chunk *c; 332 size_t len = buffer_string_length(mem); 333 if (len < 256 && chunkqueue_append_mem_extend_chunk(cq, mem->ptr, len)) return; 334 335 c = chunkqueue_append_mem_chunk(cq, chunk_buf_sz); 336 cq->bytes_in += len; 337 buffer_move(c->mem, mem); 338 } 339 340 341 void chunkqueue_append_mem(chunkqueue *cq, const char * mem, size_t len) { 342 chunk *c; 343 if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len)) 344 return; 345 346 c = chunkqueue_append_mem_chunk(cq, len+1); 347 cq->bytes_in += len; 348 buffer_copy_string_len(c->mem, mem, len); 349 } 350 351 352 void chunkqueue_append_mem_min(chunkqueue *cq, const char * mem, size_t len) { 353 chunk *c; 354 if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len)) 355 return; 356 357 c = chunk_init(len+1); 358 chunkqueue_append_chunk(cq, c); 359 cq->bytes_in += len; 360 buffer_copy_string_len(c->mem, mem, len); 361 } 362 363 364 void chunkqueue_append_chunkqueue(chunkqueue *cq, chunkqueue *src) { 365 if (src == NULL || NULL == src->first) return; 366 367 if (NULL == cq->first) { 368 cq->first = src->first; 369 } else { 370 cq->last->next = src->first; 371 } 372 cq->last = src->last; 373 cq->bytes_in += (src->bytes_in - src->bytes_out); 374 375 src->first = NULL; 376 src->last = NULL; 377 src->bytes_out = src->bytes_in; 378 } 379 380 381 buffer * chunkqueue_prepend_buffer_open_sz(chunkqueue *cq, size_t sz) { 382 chunk * const c = chunkqueue_prepend_mem_chunk(cq, sz); 383 return c->mem; 384 } 385 386 387 buffer * chunkqueue_prepend_buffer_open(chunkqueue *cq) { 388 return chunkqueue_prepend_buffer_open_sz(cq, chunk_buf_sz); 389 } 390 391 392 void chunkqueue_prepend_buffer_commit(chunkqueue *cq) { 393 cq->bytes_in += buffer_string_length(cq->first->mem); 394 } 395 396 397 buffer * chunkqueue_append_buffer_open_sz(chunkqueue *cq, size_t sz) { 398 chunk * const c = chunkqueue_append_mem_chunk(cq, sz); 399 return c->mem; 400 } 401 402 403 buffer * chunkqueue_append_buffer_open(chunkqueue *cq) { 404 return chunkqueue_append_buffer_open_sz(cq, chunk_buf_sz); 405 } 406 407 408 void chunkqueue_append_buffer_commit(chunkqueue *cq) { 409 cq->bytes_in += buffer_string_length(cq->last->mem); 410 } 411 412 413 static void chunkqueue_remove_empty_chunks(chunkqueue *cq); 414 415 416 char * chunkqueue_get_memory(chunkqueue *cq, size_t *len) { 417 size_t sz = *len ? *len : (chunk_buf_sz >> 1); 418 buffer *b; 419 chunk *c = cq->last; 420 if (NULL != c && MEM_CHUNK == c->type) { 421 /* return pointer into existing buffer if large enough */ 422 size_t avail = buffer_string_space(c->mem); 423 if (avail >= sz) { 424 *len = avail; 425 b = c->mem; 426 return b->ptr + buffer_string_length(b); 427 } 428 } 429 430 /* allocate new chunk */ 431 b = chunkqueue_append_buffer_open_sz(cq, sz); 432 *len = buffer_string_space(b); 433 return b->ptr; 434 } 435 436 void chunkqueue_use_memory(chunkqueue *cq, size_t len) { 437 buffer *b; 438 439 force_assert(NULL != cq); 440 force_assert(NULL != cq->last && MEM_CHUNK == cq->last->type); 441 b = cq->last->mem; 442 443 if (len > 0) { 444 buffer_commit(b, len); 445 cq->bytes_in += len; 446 } else if (buffer_string_is_empty(b)) { 447 /* scan chunkqueue to remove empty last chunk 448 * (generally not expecting a deep queue) */ 449 chunkqueue_remove_empty_chunks(cq); 450 } 451 } 452 453 void chunkqueue_set_tempdirs_default (array *tempdirs, off_t upload_temp_file_size) { 454 chunkqueue_default_tempdirs = tempdirs; 455 chunkqueue_default_tempfile_size 456 = (0 == upload_temp_file_size) ? DEFAULT_TEMPFILE_SIZE 457 : (upload_temp_file_size > MAX_TEMPFILE_SIZE) ? MAX_TEMPFILE_SIZE 458 : upload_temp_file_size; 459 } 460 461 void chunkqueue_set_tempdirs(chunkqueue *cq, array *tempdirs, off_t upload_temp_file_size) { 462 force_assert(NULL != cq); 463 cq->tempdirs = tempdirs; 464 cq->upload_temp_file_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 cq->tempdir_idx = 0; 469 } 470 471 void chunkqueue_steal(chunkqueue *dest, chunkqueue *src, off_t len) { 472 while (len > 0) { 473 chunk *c = src->first; 474 off_t clen = 0, use; 475 476 if (NULL == c) break; 477 478 clen = chunk_remaining_length(c); 479 if (0 == clen) { 480 /* drop empty chunk */ 481 src->first = c->next; 482 if (c == src->last) src->last = NULL; 483 chunk_release(c); 484 continue; 485 } 486 487 use = len >= clen ? clen : len; 488 len -= use; 489 490 if (use == clen) { 491 /* move complete chunk */ 492 src->first = c->next; 493 if (c == src->last) src->last = NULL; 494 495 chunkqueue_append_chunk(dest, c); 496 dest->bytes_in += use; 497 } else { 498 /* partial chunk with length "use" */ 499 500 switch (c->type) { 501 case MEM_CHUNK: 502 chunkqueue_append_mem(dest, c->mem->ptr + c->offset, use); 503 break; 504 case FILE_CHUNK: 505 /* tempfile flag is in "last" chunk after the split */ 506 chunkqueue_append_file(dest, c->mem, c->file.start + c->offset, use); 507 break; 508 } 509 510 c->offset += use; 511 force_assert(0 == len); 512 } 513 514 src->bytes_out += use; 515 } 516 } 517 518 static chunk *chunkqueue_get_append_tempfile(server *srv, chunkqueue *cq) { 519 chunk *c; 520 buffer *template = buffer_init_string("/var/tmp/lighttpd-upload-XXXXXX"); 521 int fd = -1; 522 523 if (cq->tempdirs && cq->tempdirs->used) { 524 /* we have several tempdirs, only if all of them fail we jump out */ 525 526 for (errno = EIO; cq->tempdir_idx < cq->tempdirs->used; ++cq->tempdir_idx) { 527 data_string *ds = (data_string *)cq->tempdirs->data[cq->tempdir_idx]; 528 529 buffer_copy_buffer(template, ds->value); 530 buffer_append_path_len(template, CONST_STR_LEN("lighttpd-upload-XXXXXX")); 531 if (-1 != (fd = fdevent_mkstemp_append(template->ptr))) break; 532 } 533 } else { 534 fd = fdevent_mkstemp_append(template->ptr); 535 } 536 537 if (fd < 0) { 538 /* (report only the last error to mkstemp() 539 * if multiple temp dirs attempted) */ 540 log_error_write(srv, __FILE__, __LINE__, "sbs", 541 "opening temp-file failed:", 542 template, strerror(errno)); 543 buffer_free(template); 544 return NULL; 545 } 546 547 c = chunkqueue_append_file_chunk(cq, template, 0, 0); 548 c->file.fd = fd; 549 c->file.is_temp = 1; 550 551 buffer_free(template); 552 553 return c; 554 } 555 556 int chunkqueue_append_mem_to_tempfile(server *srv, chunkqueue *dest, const char *mem, size_t len) { 557 chunk *dst_c; 558 ssize_t written; 559 560 do { 561 /* 562 * if the last chunk is 563 * - smaller than dest->upload_temp_file_size 564 * - not read yet (offset == 0) 565 * -> append to it (so it might actually become larger than dest->upload_temp_file_size) 566 * otherwise 567 * -> create a new chunk 568 * 569 * */ 570 571 dst_c = dest->last; 572 if (NULL != dst_c 573 && FILE_CHUNK == dst_c->type 574 && dst_c->file.is_temp 575 && dst_c->file.fd >= 0 576 && 0 == dst_c->offset) { 577 /* ok, take the last chunk for our job */ 578 579 if (dst_c->file.length >= (off_t)dest->upload_temp_file_size) { 580 /* the chunk is too large now, close it */ 581 int rc = close(dst_c->file.fd); 582 dst_c->file.fd = -1; 583 if (0 != rc) { 584 log_error_write(srv, __FILE__, __LINE__, "sbss", 585 "close() temp-file", dst_c->mem, "failed:", 586 strerror(errno)); 587 return -1; 588 } 589 dst_c = NULL; 590 } 591 } else { 592 dst_c = NULL; 593 } 594 595 if (NULL == dst_c && NULL == (dst_c = chunkqueue_get_append_tempfile(srv, dest))) { 596 return -1; 597 } 598 #ifdef __COVERITY__ 599 if (dst_c->file.fd < 0) return -1; 600 #endif 601 602 /* (dst_c->file.fd >= 0) */ 603 /* coverity[negative_returns : FALSE] */ 604 written = write(dst_c->file.fd, mem, len); 605 606 if ((size_t) written == len) { 607 dst_c->file.length += len; 608 dest->bytes_in += len; 609 610 return 0; 611 } else if (written >= 0) { 612 /*(assume EINTR if partial write and retry write(); 613 * retry write() might fail with ENOSPC if no more space on volume)*/ 614 dest->bytes_in += written; 615 mem += written; 616 len -= (size_t)written; 617 dst_c->file.length += (size_t)written; 618 /* continue; retry */ 619 } else if (errno == EINTR) { 620 /* continue; retry */ 621 } else { 622 int retry = (errno == ENOSPC && dest->tempdirs && ++dest->tempdir_idx < dest->tempdirs->used); 623 if (!retry) { 624 log_error_write(srv, __FILE__, __LINE__, "sbs", 625 "write() temp-file", dst_c->mem, "failed:", 626 strerror(errno)); 627 } 628 629 if (0 == chunk_remaining_length(dst_c)) { 630 /*(remove empty chunk and unlink tempfile)*/ 631 chunkqueue_remove_empty_chunks(dest); 632 } else {/*(close tempfile; avoid later attempts to append)*/ 633 int rc = close(dst_c->file.fd); 634 dst_c->file.fd = -1; 635 if (0 != rc) { 636 log_error_write(srv, __FILE__, __LINE__, "sbss", 637 "close() temp-file", dst_c->mem, "failed:", 638 strerror(errno)); 639 return -1; 640 } 641 } 642 if (!retry) break; /* return -1; */ 643 644 /* continue; retry */ 645 } 646 647 } while (dst_c); 648 649 return -1; 650 } 651 652 int chunkqueue_steal_with_tempfiles(server *srv, chunkqueue *dest, chunkqueue *src, off_t len) { 653 while (len > 0) { 654 chunk *c = src->first; 655 off_t clen = 0, use; 656 657 if (NULL == c) break; 658 659 clen = chunk_remaining_length(c); 660 if (0 == clen) { 661 /* drop empty chunk */ 662 src->first = c->next; 663 if (c == src->last) src->last = NULL; 664 chunk_release(c); 665 continue; 666 } 667 668 use = (len >= clen) ? clen : len; 669 len -= use; 670 671 switch (c->type) { 672 case FILE_CHUNK: 673 if (use == clen) { 674 /* move complete chunk */ 675 src->first = c->next; 676 if (c == src->last) src->last = NULL; 677 chunkqueue_append_chunk(dest, c); 678 dest->bytes_in += use; 679 } else { 680 /* partial chunk with length "use" */ 681 /* tempfile flag is in "last" chunk after the split */ 682 chunkqueue_append_file(dest, c->mem, c->file.start + c->offset, use); 683 684 c->offset += use; 685 force_assert(0 == len); 686 } 687 break; 688 689 case MEM_CHUNK: 690 /* store "use" bytes from memory chunk in tempfile */ 691 if (0 != chunkqueue_append_mem_to_tempfile(srv, dest, c->mem->ptr + c->offset, use)) { 692 return -1; 693 } 694 695 if (use == clen) { 696 /* finished chunk */ 697 src->first = c->next; 698 if (c == src->last) src->last = NULL; 699 chunk_release(c); 700 } else { 701 /* partial chunk */ 702 c->offset += use; 703 force_assert(0 == len); 704 } 705 break; 706 } 707 708 src->bytes_out += use; 709 } 710 711 return 0; 712 } 713 714 off_t chunkqueue_length(chunkqueue *cq) { 715 off_t len = 0; 716 chunk *c; 717 718 for (c = cq->first; c; c = c->next) { 719 len += chunk_remaining_length(c); 720 } 721 722 return len; 723 } 724 725 void chunkqueue_mark_written(chunkqueue *cq, off_t len) { 726 off_t written = len; 727 chunk *c; 728 force_assert(len >= 0); 729 730 for (c = cq->first; NULL != c; c = cq->first) { 731 off_t c_len = chunk_remaining_length(c); 732 733 if (0 == written && 0 != c_len) break; /* no more finished chunks */ 734 735 if (written >= c_len) { /* chunk got finished */ 736 c->offset += c_len; 737 written -= c_len; 738 739 cq->first = c->next; 740 if (c == cq->last) cq->last = NULL; 741 chunk_release(c); 742 } else { /* partial chunk */ 743 c->offset += written; 744 written = 0; 745 break; /* chunk not finished */ 746 } 747 } 748 749 force_assert(0 == written); 750 cq->bytes_out += len; 751 } 752 753 void chunkqueue_remove_finished_chunks(chunkqueue *cq) { 754 chunk *c; 755 756 for (c = cq->first; c; c = cq->first) { 757 if (0 != chunk_remaining_length(c)) break; /* not finished yet */ 758 759 cq->first = c->next; 760 if (c == cq->last) 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 if (chunkqueue_is_empty(cq)) return; 769 770 for (c = cq->first; c && c->next; c = c->next) { 771 if (0 == chunk_remaining_length(c->next)) { 772 chunk *empty = c->next; 773 c->next = empty->next; 774 if (empty == cq->last) cq->last = c; 775 chunk_release(empty); 776 } 777 } 778 } 779 780 int chunkqueue_open_file_chunk(server *srv, chunkqueue *cq) { 781 chunk* const c = cq->first; 782 off_t offset, toSend; 783 struct stat st; 784 785 force_assert(NULL != c); 786 force_assert(FILE_CHUNK == c->type); 787 force_assert(c->offset >= 0 && c->offset <= c->file.length); 788 789 offset = c->file.start + c->offset; 790 toSend = c->file.length - c->offset; 791 792 if (-1 == c->file.fd) { 793 /* (permit symlinks; should already have been checked. However, TOC-TOU remains) */ 794 if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, 1, O_RDONLY, 0))) { 795 log_error_write(srv, __FILE__, __LINE__, "ssb", "open failed:", strerror(errno), c->mem); 796 return -1; 797 } 798 } 799 800 /*(skip file size checks if file is temp file created by lighttpd)*/ 801 if (c->file.is_temp) return 0; 802 803 if (-1 == fstat(c->file.fd, &st)) { 804 log_error_write(srv, __FILE__, __LINE__, "ss", "fstat failed:", strerror(errno)); 805 return -1; 806 } 807 808 if (offset > st.st_size || toSend > st.st_size || offset > st.st_size - toSend) { 809 log_error_write(srv, __FILE__, __LINE__, "sb", "file shrunk:", c->mem); 810 return -1; 811 } 812 813 return 0; 814 } 815