xref: /lighttpd1.4/src/chunk.c (revision 5ba17526)
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(void) {
60 	chunkqueue *cq;
61 
62 	cq = calloc(1, sizeof(*cq));
63 	force_assert(NULL != cq);
64 
65 	cq->first = NULL;
66 	cq->last = NULL;
67 
68 	cq->tempdirs              = chunkqueue_default_tempdirs;
69 	cq->upload_temp_file_size = chunkqueue_default_tempfile_size;
70 
71 	return cq;
72 }
73 
74 __attribute_returns_nonnull__
75 static chunk *chunk_init(size_t sz) {
76 	chunk *c;
77 
78 	c = calloc(1, sizeof(*c));
79 	force_assert(NULL != c);
80 
81 	c->type = MEM_CHUNK;
82 	c->mem = buffer_init();
83 	c->file.start = c->file.length = c->file.mmap.offset = 0;
84 	c->file.fd = -1;
85 	c->file.mmap.start = MAP_FAILED;
86 	c->file.mmap.length = 0;
87 	c->file.is_temp = 0;
88 	c->offset = 0;
89 	c->next = NULL;
90 
91 	buffer_string_prepare_copy(c->mem, sz-1);
92 
93 	return c;
94 }
95 
96 static void chunk_reset_file_chunk(chunk *c) {
97 	if (c->file.is_temp && !chunk_buffer_string_is_empty(c->mem)) {
98 		unlink(c->mem->ptr);
99 	}
100 	if (c->file.fd != -1) {
101 		close(c->file.fd);
102 		c->file.fd = -1;
103 	}
104 	if (MAP_FAILED != c->file.mmap.start) {
105 		munmap(c->file.mmap.start, c->file.mmap.length);
106 		c->file.mmap.start = MAP_FAILED;
107 	}
108 	c->file.start = c->file.length = c->file.mmap.offset = 0;
109 	c->file.mmap.length = 0;
110 	c->file.is_temp = 0;
111 	c->type = MEM_CHUNK;
112 }
113 
114 static void chunk_reset(chunk *c) {
115 	if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c);
116 
117 	buffer_clear(c->mem);
118 	c->offset = 0;
119 }
120 
121 static void chunk_free(chunk *c) {
122 	if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c);
123 	buffer_free(c->mem);
124 	free(c);
125 }
126 
127 buffer * chunk_buffer_acquire(void) {
128     chunk *c;
129     buffer *b;
130     if (chunks) {
131         c = chunks;
132         chunks = c->next;
133     }
134     else {
135         c = chunk_init(chunk_buf_sz);
136     }
137     c->next = chunk_buffers;
138     chunk_buffers = c;
139     b = c->mem;
140     c->mem = NULL;
141     return b;
142 }
143 
144 void chunk_buffer_release(buffer *b) {
145     if (NULL == b) return;
146     if (b->size >= chunk_buf_sz && chunk_buffers) {
147         chunk *c = chunk_buffers;
148         chunk_buffers = c->next;
149         c->mem = b;
150         c->next = chunks;
151         chunks = c;
152         buffer_clear(b);
153     }
154     else {
155         buffer_free(b);
156     }
157 }
158 
159 __attribute_returns_nonnull__
160 static chunk * chunk_acquire(size_t sz) {
161     if (sz <= chunk_buf_sz) {
162         if (chunks) {
163             chunk *c = chunks;
164             chunks = c->next;
165             return c;
166         }
167         sz = chunk_buf_sz;
168     }
169     else {
170         sz = (sz + 8191) & ~8191uL;
171         /* future: might have buckets of certain sizes, up to socket buf sizes*/
172         if (chunks_oversized && chunks_oversized->mem->size >= sz) {
173             chunk *c = chunks_oversized;
174             chunks_oversized = c->next;
175             return c;
176         }
177     }
178 
179     return chunk_init(sz);
180 }
181 
182 static void chunk_release(chunk *c) {
183     const size_t sz = c->mem->size;
184     if (sz == chunk_buf_sz) {
185         chunk_reset(c);
186         c->next = chunks;
187         chunks = c;
188     }
189     else if (sz > chunk_buf_sz) {
190         chunk_reset(c);
191         chunk **co = &chunks_oversized;
192         while (*co && sz < (*co)->mem->size) co = &(*co)->next;
193         c->next = *co;
194         *co = c;
195     }
196     else {
197         chunk_free(c);
198     }
199 }
200 
201 void chunkqueue_chunk_pool_clear(void)
202 {
203     for (chunk *next, *c = chunks; c; c = next) {
204         next = c->next;
205         chunk_free(c);
206     }
207     chunks = NULL;
208     for (chunk *next, *c = chunks_oversized; c; c = next) {
209         next = c->next;
210         chunk_free(c);
211     }
212     chunks_oversized = NULL;
213 }
214 
215 void chunkqueue_chunk_pool_free(void)
216 {
217     chunkqueue_chunk_pool_clear();
218     for (chunk *next, *c = chunk_buffers; c; c = next) {
219         next = c->next;
220         c->mem = buffer_init(); /*(chunk_reset() expects c->mem != NULL)*/
221         chunk_free(c);
222     }
223     chunk_buffers = NULL;
224 }
225 
226 __attribute_pure__
227 static off_t chunk_remaining_length(const chunk *c) {
228     /* MEM_CHUNK or FILE_CHUNK */
229     return (c->type == MEM_CHUNK
230               ? (off_t)chunk_buffer_string_length(c->mem)
231               : c->file.length)
232            - c->offset;
233 }
234 
235 static void chunkqueue_release_chunks(chunkqueue *cq) {
236     cq->last = NULL;
237     for (chunk *c; (c = cq->first); ) {
238         cq->first = c->next;
239         chunk_release(c);
240     }
241 }
242 
243 void chunkqueue_free(chunkqueue *cq) {
244     if (NULL == cq) return;
245     chunkqueue_release_chunks(cq);
246     free(cq);
247 }
248 
249 static void chunkqueue_prepend_chunk(chunkqueue * const restrict cq, chunk * const restrict c) {
250     if (NULL == (c->next = cq->first)) cq->last = c;
251     cq->first = c;
252 }
253 
254 static void chunkqueue_append_chunk(chunkqueue * const restrict cq, chunk * const restrict c) {
255     c->next = NULL;
256     *(cq->last ? &cq->last->next : &cq->first) = c;
257     cq->last = c;
258 }
259 
260 __attribute_returns_nonnull__
261 static chunk * chunkqueue_prepend_mem_chunk(chunkqueue *cq, size_t sz) {
262     chunk *c = chunk_acquire(sz);
263     chunkqueue_prepend_chunk(cq, c);
264     return c;
265 }
266 
267 __attribute_returns_nonnull__
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 __attribute_returns_nonnull__
275 static chunk * chunkqueue_append_file_chunk(chunkqueue * const restrict cq, const buffer * const restrict fn, off_t offset, off_t len) {
276     chunk *c = chunk_acquire(buffer_string_length(fn)+1);
277     chunkqueue_append_chunk(cq, c);
278     c->type = FILE_CHUNK;
279     c->file.start = offset;
280     c->file.length = len;
281     cq->bytes_in += len;
282     buffer_copy_buffer(c->mem, fn);
283     return c;
284 }
285 
286 void chunkqueue_reset(chunkqueue *cq) {
287     chunkqueue_release_chunks(cq);
288     cq->bytes_in = 0;
289     cq->bytes_out = 0;
290     cq->tempdir_idx = 0;
291 }
292 
293 void chunkqueue_append_file_fd(chunkqueue * const restrict cq, const buffer * const restrict fn, int fd, off_t offset, off_t len) {
294     if (len > 0) {
295         (chunkqueue_append_file_chunk(cq, fn, offset, len))->file.fd = fd;
296     }
297     else {
298         close(fd);
299     }
300 }
301 
302 void chunkqueue_append_file(chunkqueue * const restrict cq, const buffer * const restrict fn, off_t offset, off_t len) {
303     if (len > 0) {
304         chunkqueue_append_file_chunk(cq, fn, offset, len);
305     }
306 }
307 
308 
309 static int chunkqueue_append_mem_extend_chunk(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) {
310 	chunk *c = cq->last;
311 	if (0 == len) return 1;
312 	if (c != NULL && c->type == MEM_CHUNK
313 	    && chunk_buffer_string_space(c->mem) >= len) {
314 		buffer_append_string_len(c->mem, mem, len);
315 		cq->bytes_in += len;
316 		return 1;
317 	}
318 	return 0;
319 }
320 
321 
322 void chunkqueue_append_buffer(chunkqueue * const restrict cq, buffer * const restrict mem) {
323 	chunk *c;
324 	size_t len = buffer_string_length(mem);
325 	if (len < 256 && chunkqueue_append_mem_extend_chunk(cq, mem->ptr, len)) return;
326 
327 	c = chunkqueue_append_mem_chunk(cq, chunk_buf_sz);
328 	cq->bytes_in += len;
329 	buffer_move(c->mem, mem);
330 }
331 
332 
333 void chunkqueue_append_mem(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) {
334 	chunk *c;
335 	if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len))
336 		return;
337 
338 	c = chunkqueue_append_mem_chunk(cq, len+1);
339 	cq->bytes_in += len;
340 	buffer_copy_string_len(c->mem, mem, len);
341 }
342 
343 
344 void chunkqueue_append_mem_min(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) {
345 	chunk *c;
346 	if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len))
347 		return;
348 
349 	c = chunk_init(len+1);
350 	chunkqueue_append_chunk(cq, c);
351 	cq->bytes_in += len;
352 	buffer_copy_string_len(c->mem, mem, len);
353 }
354 
355 
356 void chunkqueue_append_chunkqueue(chunkqueue * const restrict cq, chunkqueue * const restrict src) {
357 	if (src == NULL || NULL == src->first) return;
358 
359 	if (NULL == cq->first) {
360 		cq->first = src->first;
361 	} else {
362 		cq->last->next = src->first;
363 	}
364 	cq->last = src->last;
365 	cq->bytes_in += chunkqueue_length(src);
366 
367 	src->first = NULL;
368 	src->last = NULL;
369 	src->bytes_out = src->bytes_in;
370 }
371 
372 
373 buffer * chunkqueue_prepend_buffer_open_sz(chunkqueue *cq, size_t sz) {
374 	chunk * const c = chunkqueue_prepend_mem_chunk(cq, sz);
375 	return c->mem;
376 }
377 
378 
379 buffer * chunkqueue_prepend_buffer_open(chunkqueue *cq) {
380 	return chunkqueue_prepend_buffer_open_sz(cq, chunk_buf_sz);
381 }
382 
383 
384 void chunkqueue_prepend_buffer_commit(chunkqueue *cq) {
385 	cq->bytes_in += chunk_buffer_string_length(cq->first->mem);
386 }
387 
388 
389 buffer * chunkqueue_append_buffer_open_sz(chunkqueue *cq, size_t sz) {
390 	chunk * const c = chunkqueue_append_mem_chunk(cq, sz);
391 	return c->mem;
392 }
393 
394 
395 buffer * chunkqueue_append_buffer_open(chunkqueue *cq) {
396 	return chunkqueue_append_buffer_open_sz(cq, chunk_buf_sz);
397 }
398 
399 
400 void chunkqueue_append_buffer_commit(chunkqueue *cq) {
401 	cq->bytes_in += chunk_buffer_string_length(cq->last->mem);
402 }
403 
404 
405 static void chunkqueue_remove_empty_chunks(chunkqueue *cq);
406 
407 
408 char * chunkqueue_get_memory(chunkqueue * const restrict cq, size_t * const restrict len) {
409 	size_t sz = *len ? *len : (chunk_buf_sz >> 1);
410 	buffer *b;
411 	chunk *c = cq->last;
412 	if (NULL != c && MEM_CHUNK == c->type) {
413 		/* return pointer into existing buffer if large enough */
414 		size_t avail = chunk_buffer_string_space(c->mem);
415 		if (avail >= sz) {
416 			*len = avail;
417 			b = c->mem;
418 			return b->ptr + chunk_buffer_string_length(b);
419 		}
420 	}
421 
422 	/* allocate new chunk */
423 	b = chunkqueue_append_buffer_open_sz(cq, sz);
424 	*len = chunk_buffer_string_space(b);
425 	return b->ptr;
426 }
427 
428 void chunkqueue_use_memory(chunkqueue * const restrict cq, chunk *ckpt, size_t len) {
429     buffer *b = cq->last->mem;
430 
431     if (len > 0) {
432         buffer_commit(b, len);
433         cq->bytes_in += len;
434         if (cq->last == ckpt || NULL == ckpt || MEM_CHUNK != ckpt->type
435             || len > chunk_buffer_string_space(ckpt->mem)) return;
436 
437         buffer_append_string_buffer(ckpt->mem, b);
438     }
439     else if (!chunk_buffer_string_is_empty(b)) { /*(cq->last == ckpt)*/
440         return; /* last chunk is not empty */
441     }
442 
443     /* remove empty last chunk */
444     chunk_release(cq->last);
445     cq->last = ckpt;
446     *(ckpt ? &ckpt->next : &cq->first) = NULL;
447 }
448 
449 void chunkqueue_update_file(chunkqueue * const restrict cq, chunk *c, off_t len) {
450     /*assert(c->type == FILE_CHUNK);*/
451     c->file.length += len;
452     cq->bytes_in += len;
453 }
454 
455 void chunkqueue_set_tempdirs_default (const array *tempdirs, off_t upload_temp_file_size) {
456 	chunkqueue_default_tempdirs = tempdirs;
457 	chunkqueue_default_tempfile_size
458 		= (0 == upload_temp_file_size)                ? DEFAULT_TEMPFILE_SIZE
459 		: (upload_temp_file_size > MAX_TEMPFILE_SIZE) ? MAX_TEMPFILE_SIZE
460 		                                              : upload_temp_file_size;
461 }
462 
463 void chunkqueue_set_tempdirs(chunkqueue * const restrict cq, const array * const restrict tempdirs, off_t upload_temp_file_size) {
464 	force_assert(NULL != cq);
465 	cq->tempdirs = tempdirs;
466 	cq->upload_temp_file_size
467 		= (0 == upload_temp_file_size)                ? DEFAULT_TEMPFILE_SIZE
468 		: (upload_temp_file_size > MAX_TEMPFILE_SIZE) ? MAX_TEMPFILE_SIZE
469 		                                              : upload_temp_file_size;
470 	cq->tempdir_idx = 0;
471 }
472 
473 void chunkqueue_steal(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len) {
474 	while (len > 0) {
475 		chunk *c = src->first;
476 		off_t clen = 0, use;
477 
478 		if (NULL == c) break;
479 
480 		clen = chunk_remaining_length(c);
481 		if (0 == clen) {
482 			/* drop empty chunk */
483 			src->first = c->next;
484 			if (c == src->last) src->last = NULL;
485 			chunk_release(c);
486 			continue;
487 		}
488 
489 		use = len >= clen ? clen : len;
490 		len -= use;
491 
492 		if (use == clen) {
493 			/* move complete chunk */
494 			src->first = c->next;
495 			if (c == src->last) src->last = NULL;
496 
497 			chunkqueue_append_chunk(dest, c);
498 			dest->bytes_in += use;
499 		} else {
500 			/* partial chunk with length "use" */
501 
502 			switch (c->type) {
503 			case MEM_CHUNK:
504 				chunkqueue_append_mem(dest, c->mem->ptr + c->offset, use);
505 				break;
506 			case FILE_CHUNK:
507 				/* tempfile flag is in "last" chunk after the split */
508 				chunkqueue_append_file(dest, c->mem, c->file.start + c->offset, use);
509 				break;
510 			}
511 
512 			c->offset += use;
513 			force_assert(0 == len);
514 		}
515 
516 		src->bytes_out += use;
517 	}
518 }
519 
520 static chunk *chunkqueue_get_append_tempfile(chunkqueue * const restrict cq, log_error_st * const restrict errh) {
521 	chunk *c;
522 	buffer *template = buffer_init_string("/var/tmp/lighttpd-upload-XXXXXX");
523 	int fd = -1;
524 
525 	if (cq->tempdirs && cq->tempdirs->used) {
526 		/* we have several tempdirs, only if all of them fail we jump out */
527 
528 		for (errno = EIO; cq->tempdir_idx < cq->tempdirs->used; ++cq->tempdir_idx) {
529 			data_string *ds = (data_string *)cq->tempdirs->data[cq->tempdir_idx];
530 
531 			buffer_copy_buffer(template, &ds->value);
532 			buffer_append_path_len(template, CONST_STR_LEN("lighttpd-upload-XXXXXX"));
533 			if (-1 != (fd = fdevent_mkstemp_append(template->ptr))) break;
534 		}
535 	} else {
536 		fd = fdevent_mkstemp_append(template->ptr);
537 	}
538 
539 	if (fd < 0) {
540 		/* (report only the last error to mkstemp()
541 		 *  if multiple temp dirs attempted) */
542 		log_perror(errh, __FILE__, __LINE__,
543 		  "opening temp-file failed: %s", template->ptr);
544 		buffer_free(template);
545 		return NULL;
546 	}
547 
548 	c = chunkqueue_append_file_chunk(cq, template, 0, 0);
549 	c->file.fd = fd;
550 	c->file.is_temp = 1;
551 
552 	buffer_free(template);
553 
554 	return c;
555 }
556 
557 int chunkqueue_append_mem_to_tempfile(chunkqueue * const restrict dest, const char * restrict mem, size_t len, log_error_st * const restrict errh) {
558 	chunk *dst_c;
559 	ssize_t written;
560 
561 	do {
562 		/*
563 		 * if the last chunk is
564 		 * - smaller than dest->upload_temp_file_size
565 		 * - not read yet (offset == 0)
566 		 * -> append to it (so it might actually become larger than dest->upload_temp_file_size)
567 		 * otherwise
568 		 * -> create a new chunk
569 		 *
570 		 * */
571 
572 		dst_c = dest->last;
573 		if (NULL != dst_c
574 			&& FILE_CHUNK == dst_c->type
575 			&& dst_c->file.is_temp
576 			&& dst_c->file.fd >= 0
577 			&& 0 == dst_c->offset) {
578 			/* ok, take the last chunk for our job */
579 
580 			if (dst_c->file.length >= (off_t)dest->upload_temp_file_size) {
581 				/* the chunk is too large now, close it */
582 				int rc = close(dst_c->file.fd);
583 				dst_c->file.fd = -1;
584 				if (0 != rc) {
585 					log_perror(errh, __FILE__, __LINE__,
586 					  "close() temp-file %s failed", dst_c->mem->ptr);
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(dest, errh))) {
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_perror(errh, __FILE__, __LINE__,
625 				  "write() temp-file %s failed", dst_c->mem->ptr);
626 			}
627 
628 			if (0 == chunk_remaining_length(dst_c)) {
629 				/*(remove empty chunk and unlink tempfile)*/
630 				chunkqueue_remove_empty_chunks(dest);
631 			} else {/*(close tempfile; avoid later attempts to append)*/
632 				int rc = close(dst_c->file.fd);
633 				dst_c->file.fd = -1;
634 				if (0 != rc) {
635 					log_perror(errh, __FILE__, __LINE__,
636 					  "close() temp-file %s failed", dst_c->mem->ptr);
637 					return -1;
638 				}
639 			}
640 			if (!retry) break; /* return -1; */
641 
642 			/* continue; retry */
643 		}
644 
645 	} while (dst_c);
646 
647 	return -1;
648 }
649 
650 int chunkqueue_steal_with_tempfiles(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len, log_error_st * const restrict errh) {
651 	while (len > 0) {
652 		chunk *c = src->first;
653 		off_t clen = 0, use;
654 
655 		if (NULL == c) break;
656 
657 		clen = chunk_remaining_length(c);
658 		if (0 == clen) {
659 			/* drop empty chunk */
660 			src->first = c->next;
661 			if (c == src->last) src->last = NULL;
662 			chunk_release(c);
663 			continue;
664 		}
665 
666 		use = (len >= clen) ? clen : len;
667 		len -= use;
668 
669 		switch (c->type) {
670 		case FILE_CHUNK:
671 			if (use == clen) {
672 				/* move complete chunk */
673 				src->first = c->next;
674 				if (c == src->last) src->last = NULL;
675 				chunkqueue_append_chunk(dest, c);
676 				dest->bytes_in += use;
677 			} else {
678 				/* partial chunk with length "use" */
679 				/* tempfile flag is in "last" chunk after the split */
680 				chunkqueue_append_file(dest, c->mem, c->file.start + c->offset, use);
681 
682 				c->offset += use;
683 				force_assert(0 == len);
684 			}
685 			break;
686 
687 		case MEM_CHUNK:
688 			/* store "use" bytes from memory chunk in tempfile */
689 			if (0 != chunkqueue_append_mem_to_tempfile(dest, c->mem->ptr + c->offset, use, errh)) {
690 				return -1;
691 			}
692 
693 			if (use == clen) {
694 				/* finished chunk */
695 				src->first = c->next;
696 				if (c == src->last) src->last = NULL;
697 				chunk_release(c);
698 			} else {
699 				/* partial chunk */
700 				c->offset += use;
701 				force_assert(0 == len);
702 			}
703 			break;
704 		}
705 
706 		src->bytes_out += use;
707 	}
708 
709 	return 0;
710 }
711 
712 void chunkqueue_mark_written(chunkqueue *cq, off_t len) {
713     cq->bytes_out += len;
714 
715     for (chunk *c; (c = cq->first); ) {
716         off_t c_len = chunk_remaining_length(c);
717         if (len >= c_len) { /* chunk got finished */
718             len -= c_len;
719             cq->first = c->next;
720             chunk_release(c);
721             if (0 == len) break;
722         }
723         else { /* partial chunk */
724             c->offset += len;
725             return; /* chunk not finished */
726         }
727     }
728 
729     if (NULL == cq->first)
730         cq->last = NULL;
731     else
732         chunkqueue_remove_finished_chunks(cq);
733 }
734 
735 void chunkqueue_remove_finished_chunks(chunkqueue *cq) {
736     for (chunk *c; (c = cq->first) && 0 == chunk_remaining_length(c); ){
737         if (NULL == (cq->first = c->next)) cq->last = NULL;
738         chunk_release(c);
739     }
740 }
741 
742 static void chunkqueue_remove_empty_chunks(chunkqueue *cq) {
743 	chunk *c;
744 	chunkqueue_remove_finished_chunks(cq);
745 
746 	for (c = cq->first; c && c->next; c = c->next) {
747 		if (0 == chunk_remaining_length(c->next)) {
748 			chunk *empty = c->next;
749 			c->next = empty->next;
750 			if (empty == cq->last) cq->last = c;
751 			chunk_release(empty);
752 		}
753 	}
754 }
755 
756 void chunkqueue_compact_mem(chunkqueue *cq, size_t clen) {
757     chunk *c = cq->first;
758     buffer *b = c->mem;
759     size_t len = buffer_string_length(b) - c->offset;
760     if (b->size > clen) {
761         if (0 != c->offset) {
762             memmove(b->ptr, b->ptr+c->offset, len);
763             buffer_string_set_length(b, len);
764             c->offset = 0;
765         }
766     }
767     else {
768         b = chunkqueue_prepend_buffer_open_sz(cq, clen + 8192);
769         buffer_append_string_len(b, c->mem->ptr + c->offset, len);
770         cq->first->next = c->next;
771         if (NULL == c->next) cq->last = cq->first;
772         chunk_release(c);
773         c = cq->first;
774     }
775     for (chunk *fc = c; ((clen -= len) && (c = fc->next)); ) {
776         len = buffer_string_length(c->mem) - c->offset;
777         if (len > clen) len = clen;
778         buffer_append_string_len(b, c->mem->ptr + c->offset, len);
779         fc->next = c->next;
780         if (NULL == c->next) cq->last = fc;
781         chunk_release(c);
782     }
783     /* chunkqueue_prepend_buffer_commit() is not called here;
784      * no data added/removed from chunkqueue; consolidated only */
785 }
786 
787 int chunkqueue_open_file_chunk(chunkqueue * const restrict cq, log_error_st * const restrict errh) {
788 	chunk* const c = cq->first;
789 	off_t offset, toSend;
790 	struct stat st;
791 
792 	force_assert(NULL != c);
793 	force_assert(FILE_CHUNK == c->type);
794 	force_assert(c->offset >= 0 && c->offset <= c->file.length);
795 
796 	offset = c->file.start + c->offset;
797 	toSend = c->file.length - c->offset;
798 
799 	if (-1 == c->file.fd) {
800 		/* (permit symlinks; should already have been checked.  However, TOC-TOU remains) */
801 		if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, 1, O_RDONLY, 0))) {
802 			log_perror(errh, __FILE__, __LINE__, "open failed: %s",c->mem->ptr);
803 			return -1;
804 		}
805 	}
806 
807 	/*(skip file size checks if file is temp file created by lighttpd)*/
808 	if (c->file.is_temp) return 0;
809 
810 	if (-1 == fstat(c->file.fd, &st)) {
811 		log_perror(errh, __FILE__, __LINE__, "fstat failed");
812 		return -1;
813 	}
814 
815 	if (offset > st.st_size || toSend > st.st_size || offset > st.st_size - toSend) {
816 		log_error(errh, __FILE__, __LINE__, "file shrunk: %s", c->mem->ptr);
817 		return -1;
818 	}
819 
820 	return 0;
821 }
822 
823 
824 void
825 chunkqueue_small_resp_optim (chunkqueue * const restrict cq)
826 {
827     /*(caller must verify response is small (and non-empty) before calling)*/
828 
829     /*(optimization to use fewer syscalls to send a small response by reading
830      * small files into memory, thereby avoiding use of sendfile() and multiple
831      * calls to writev()  (benefit for cleartext (non-TLS) and <= HTTP/1.1))
832      *(If TLS, then will shortly need to be in memory for encryption anyway)*/
833 
834     /*assert(cq->first);*/
835     chunk *c = cq->first;
836     chunk * const filec = c->next;
837     if (c->type != MEM_CHUNK || filec != cq->last || filec->type != FILE_CHUNK)
838         return;
839 
840     const int fd = filec->file.fd;
841     if (fd < 0) return; /*(require that file already be open)*/
842     off_t offset = filec->file.start + filec->offset;
843     if (0 != offset && -1 == lseek(fd, offset, SEEK_SET)) return;
844 
845     /* Note: there should be no size change in chunkqueue,
846      * so cq->bytes_in and cq->bytes_out should not be modified */
847 
848     buffer *b = c->mem;
849     off_t len = filec->file.length - filec->offset;
850     if ((size_t)len > chunk_buffer_string_space(b)) {
851         chunk * const nc = chunk_acquire((size_t)len+1);
852         c->next = nc;
853         nc->next = filec;
854         b = nc->mem;
855     }
856 
857     char * const ptr = b->ptr + chunk_buffer_string_length(b);
858     ssize_t rd;
859     offset = 0; /*(reuse offset var for offset into mem buffer)*/
860     do {
861         rd = read(fd, ptr+offset, len-offset);
862     } while (rd > 0 ? (offset += rd, len -= rd) : errno == EINTR);
863     /*(contents of chunkqueue kept valid even if error reading from file)*/
864     buffer_commit(b, offset);
865     filec->offset += offset;
866     chunkqueue_remove_empty_chunks(cq);
867 }
868