xref: /lighttpd1.4/src/chunk.c (revision 49e9f0ac)
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