xref: /lighttpd1.4/src/chunk.c (revision 2f7839e8)
1 #include "first.h"
2 
3 /**
4  * the network chunk-API
5  *
6  *
7  */
8 
9 #include "chunk.h"
10 #include "fdevent.h"
11 #include "log.h"
12 
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include "sys-mmap.h"
16 
17 #include <stdlib.h>
18 #include <fcntl.h>
19 #include <unistd.h>
20 
21 #include <errno.h>
22 #include <string.h>
23 
24 /* default 1MB, upper limit 128MB */
25 #define DEFAULT_TEMPFILE_SIZE (1 * 1024 * 1024)
26 #define MAX_TEMPFILE_SIZE (128 * 1024 * 1024)
27 
28 static size_t chunk_buf_sz = 8192;
29 static chunk *chunks, *chunks_oversized;
30 static chunk *chunk_buffers;
31 static const array *chunkqueue_default_tempdirs = NULL;
32 static off_t chunkqueue_default_tempfile_size = DEFAULT_TEMPFILE_SIZE;
33 
34 void chunkqueue_set_chunk_size (size_t sz)
35 {
36     chunk_buf_sz = sz > 0 ? ((sz + 1023) & ~1023uL) : 8192;
37 }
38 
39 void chunkqueue_set_tempdirs_default_reset (void)
40 {
41     chunkqueue_default_tempdirs = NULL;
42     chunkqueue_default_tempfile_size = DEFAULT_TEMPFILE_SIZE;
43 }
44 
45 /* chunk buffer (c->mem) is never NULL; specialize routines from buffer.h */
46 __attribute_pure__
47 static inline size_t chunk_buffer_string_length(const buffer *b) {
48     return 0 != b->used ? b->used - 1 : 0;
49 }
50 __attribute_pure__
51 static inline int chunk_buffer_string_is_empty(const buffer *b) {
52     return b->used < 2;
53 }
54 __attribute_pure__
55 static inline size_t chunk_buffer_string_space(const buffer *b) {
56     return b->size ? b->size - (b->used | (0 == b->used)) : 0;
57 }
58 
59 chunkqueue *chunkqueue_init(chunkqueue *cq) {
60 	/* (if caller passes non-NULL cq, it must be 0-init) */
61 	if (NULL == cq) {
62 		cq = calloc(1, sizeof(*cq));
63 		force_assert(NULL != cq);
64 	}
65 
66 	cq->first = NULL;
67 	cq->last = NULL;
68 
69 	cq->tempdirs              = chunkqueue_default_tempdirs;
70 	cq->upload_temp_file_size = chunkqueue_default_tempfile_size;
71 
72 	return cq;
73 }
74 
75 __attribute_returns_nonnull__
76 static chunk *chunk_init(size_t sz) {
77 	chunk *c;
78 
79 	c = calloc(1, sizeof(*c));
80 	force_assert(NULL != c);
81 
82 	c->type = MEM_CHUNK;
83 	c->mem = buffer_init();
84 	c->file.start = c->file.length = c->file.mmap.offset = 0;
85 	c->file.fd = -1;
86 	c->file.mmap.start = MAP_FAILED;
87 	c->file.mmap.length = 0;
88 	c->file.is_temp = 0;
89 	c->offset = 0;
90 	c->next = NULL;
91 
92 	buffer_string_prepare_copy(c->mem, sz-1);
93 
94 	return c;
95 }
96 
97 static void chunk_reset_file_chunk(chunk *c) {
98 	if (c->file.is_temp && !chunk_buffer_string_is_empty(c->mem)) {
99 		unlink(c->mem->ptr);
100 	}
101 	if (c->file.fd != -1) {
102 		close(c->file.fd);
103 		c->file.fd = -1;
104 	}
105 	if (MAP_FAILED != c->file.mmap.start) {
106 		munmap(c->file.mmap.start, c->file.mmap.length);
107 		c->file.mmap.start = MAP_FAILED;
108 	}
109 	c->file.start = c->file.length = c->file.mmap.offset = 0;
110 	c->file.mmap.length = 0;
111 	c->file.is_temp = 0;
112 	c->type = MEM_CHUNK;
113 }
114 
115 static void chunk_reset(chunk *c) {
116 	if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c);
117 
118 	buffer_clear(c->mem);
119 	c->offset = 0;
120 }
121 
122 static void chunk_free(chunk *c) {
123 	if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c);
124 	buffer_free(c->mem);
125 	free(c);
126 }
127 
128 buffer * chunk_buffer_acquire(void) {
129     chunk *c;
130     buffer *b;
131     if (chunks) {
132         c = chunks;
133         chunks = c->next;
134     }
135     else {
136         c = chunk_init(chunk_buf_sz);
137     }
138     c->next = chunk_buffers;
139     chunk_buffers = c;
140     b = c->mem;
141     c->mem = NULL;
142     return b;
143 }
144 
145 void chunk_buffer_release(buffer *b) {
146     if (NULL == b) return;
147     if (b->size >= chunk_buf_sz && chunk_buffers) {
148         chunk *c = chunk_buffers;
149         chunk_buffers = c->next;
150         c->mem = b;
151         c->next = chunks;
152         chunks = c;
153         buffer_clear(b);
154     }
155     else {
156         buffer_free(b);
157     }
158 }
159 
160 __attribute_returns_nonnull__
161 static chunk * chunk_acquire(size_t sz) {
162     if (sz <= chunk_buf_sz) {
163         if (chunks) {
164             chunk *c = chunks;
165             chunks = c->next;
166             return c;
167         }
168         sz = chunk_buf_sz;
169     }
170     else {
171         sz = (sz + 8191) & ~8191uL;
172         /* future: might have buckets of certain sizes, up to socket buf sizes*/
173         if (chunks_oversized && chunks_oversized->mem->size >= sz) {
174             chunk *c = chunks_oversized;
175             chunks_oversized = c->next;
176             return c;
177         }
178     }
179 
180     return chunk_init(sz);
181 }
182 
183 static void chunk_release(chunk *c) {
184     const size_t sz = c->mem->size;
185     if (sz == chunk_buf_sz) {
186         chunk_reset(c);
187         c->next = chunks;
188         chunks = c;
189     }
190     else if (sz > chunk_buf_sz) {
191         chunk_reset(c);
192         chunk **co = &chunks_oversized;
193         while (*co && sz < (*co)->mem->size) co = &(*co)->next;
194         c->next = *co;
195         *co = c;
196     }
197     else {
198         chunk_free(c);
199     }
200 }
201 
202 void chunkqueue_chunk_pool_clear(void)
203 {
204     for (chunk *next, *c = chunks; c; c = next) {
205         next = c->next;
206         chunk_free(c);
207     }
208     chunks = NULL;
209     for (chunk *next, *c = chunks_oversized; c; c = next) {
210         next = c->next;
211         chunk_free(c);
212     }
213     chunks_oversized = NULL;
214 }
215 
216 void chunkqueue_chunk_pool_free(void)
217 {
218     chunkqueue_chunk_pool_clear();
219     for (chunk *next, *c = chunk_buffers; c; c = next) {
220         next = c->next;
221         c->mem = buffer_init(); /*(chunk_reset() expects c->mem != NULL)*/
222         chunk_free(c);
223     }
224     chunk_buffers = NULL;
225 }
226 
227 __attribute_pure__
228 static off_t chunk_remaining_length(const chunk *c) {
229     /* MEM_CHUNK or FILE_CHUNK */
230     return (c->type == MEM_CHUNK
231               ? (off_t)chunk_buffer_string_length(c->mem)
232               : c->file.length)
233            - c->offset;
234 }
235 
236 static void chunkqueue_release_chunks(chunkqueue *cq) {
237     cq->last = NULL;
238     for (chunk *c; (c = cq->first); ) {
239         cq->first = c->next;
240         chunk_release(c);
241     }
242 }
243 
244 void chunkqueue_free(chunkqueue *cq) {
245     if (NULL == cq) return;
246     chunkqueue_release_chunks(cq);
247     free(cq);
248 }
249 
250 static void chunkqueue_prepend_chunk(chunkqueue * const restrict cq, chunk * const restrict c) {
251     if (NULL == (c->next = cq->first)) cq->last = c;
252     cq->first = c;
253 }
254 
255 static void chunkqueue_append_chunk(chunkqueue * const restrict cq, chunk * const restrict c) {
256     c->next = NULL;
257     *(cq->last ? &cq->last->next : &cq->first) = c;
258     cq->last = c;
259 }
260 
261 __attribute_returns_nonnull__
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 __attribute_returns_nonnull__
269 static chunk * chunkqueue_append_mem_chunk(chunkqueue *cq, size_t sz) {
270     chunk *c = chunk_acquire(sz);
271     chunkqueue_append_chunk(cq, c);
272     return c;
273 }
274 
275 __attribute_returns_nonnull__
276 static chunk * chunkqueue_append_file_chunk(chunkqueue * const restrict cq, const buffer * const restrict fn, off_t offset, off_t len) {
277     chunk *c = chunk_acquire(buffer_string_length(fn)+1);
278     chunkqueue_append_chunk(cq, c);
279     c->type = FILE_CHUNK;
280     c->file.start = offset;
281     c->file.length = len;
282     cq->bytes_in += len;
283     buffer_copy_buffer(c->mem, fn);
284     return c;
285 }
286 
287 void chunkqueue_reset(chunkqueue *cq) {
288     chunkqueue_release_chunks(cq);
289     cq->bytes_in = 0;
290     cq->bytes_out = 0;
291     cq->tempdir_idx = 0;
292 }
293 
294 void chunkqueue_append_file_fd(chunkqueue * const restrict cq, const buffer * const restrict fn, int fd, off_t offset, off_t len) {
295     if (len > 0) {
296         (chunkqueue_append_file_chunk(cq, fn, offset, len))->file.fd = fd;
297     }
298     else {
299         close(fd);
300     }
301 }
302 
303 void chunkqueue_append_file(chunkqueue * const restrict cq, const buffer * const restrict fn, off_t offset, off_t len) {
304     if (len > 0) {
305         chunkqueue_append_file_chunk(cq, fn, offset, len);
306     }
307 }
308 
309 
310 static int chunkqueue_append_mem_extend_chunk(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) {
311 	chunk *c = cq->last;
312 	if (0 == len) return 1;
313 	if (c != NULL && c->type == MEM_CHUNK
314 	    && chunk_buffer_string_space(c->mem) >= len) {
315 		buffer_append_string_len(c->mem, mem, len);
316 		cq->bytes_in += len;
317 		return 1;
318 	}
319 	return 0;
320 }
321 
322 
323 void chunkqueue_append_buffer(chunkqueue * const restrict cq, buffer * const restrict mem) {
324 	chunk *c;
325 	size_t len = buffer_string_length(mem);
326 	if (len < 256 && chunkqueue_append_mem_extend_chunk(cq, mem->ptr, len)) return;
327 
328 	c = chunkqueue_append_mem_chunk(cq, chunk_buf_sz);
329 	cq->bytes_in += len;
330 	buffer_move(c->mem, mem);
331 }
332 
333 
334 void chunkqueue_append_mem(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) {
335 	chunk *c;
336 	if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len))
337 		return;
338 
339 	c = chunkqueue_append_mem_chunk(cq, len+1);
340 	cq->bytes_in += len;
341 	buffer_copy_string_len(c->mem, mem, len);
342 }
343 
344 
345 void chunkqueue_append_mem_min(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) {
346 	chunk *c;
347 	if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len))
348 		return;
349 
350 	c = chunk_init(len+1);
351 	chunkqueue_append_chunk(cq, c);
352 	cq->bytes_in += len;
353 	buffer_copy_string_len(c->mem, mem, len);
354 }
355 
356 
357 void chunkqueue_append_chunkqueue(chunkqueue * const restrict cq, chunkqueue * const restrict src) {
358 	if (src == NULL || NULL == src->first) return;
359 
360 	if (NULL == cq->first) {
361 		cq->first = src->first;
362 	} else {
363 		cq->last->next = src->first;
364 	}
365 	cq->last = src->last;
366 	cq->bytes_in += chunkqueue_length(src);
367 
368 	src->first = NULL;
369 	src->last = NULL;
370 	src->bytes_out = src->bytes_in;
371 }
372 
373 
374 buffer * chunkqueue_prepend_buffer_open_sz(chunkqueue *cq, size_t sz) {
375 	chunk * const c = chunkqueue_prepend_mem_chunk(cq, sz);
376 	return c->mem;
377 }
378 
379 
380 buffer * chunkqueue_prepend_buffer_open(chunkqueue *cq) {
381 	return chunkqueue_prepend_buffer_open_sz(cq, chunk_buf_sz);
382 }
383 
384 
385 void chunkqueue_prepend_buffer_commit(chunkqueue *cq) {
386 	cq->bytes_in += chunk_buffer_string_length(cq->first->mem);
387 }
388 
389 
390 buffer * chunkqueue_append_buffer_open_sz(chunkqueue *cq, size_t sz) {
391 	chunk * const c = chunkqueue_append_mem_chunk(cq, sz);
392 	return c->mem;
393 }
394 
395 
396 buffer * chunkqueue_append_buffer_open(chunkqueue *cq) {
397 	return chunkqueue_append_buffer_open_sz(cq, chunk_buf_sz);
398 }
399 
400 
401 void chunkqueue_append_buffer_commit(chunkqueue *cq) {
402 	cq->bytes_in += chunk_buffer_string_length(cq->last->mem);
403 }
404 
405 
406 static void chunkqueue_remove_empty_chunks(chunkqueue *cq);
407 
408 
409 char * chunkqueue_get_memory(chunkqueue * const restrict cq, size_t * const restrict len) {
410 	size_t sz = *len ? *len : (chunk_buf_sz >> 1);
411 	buffer *b;
412 	chunk *c = cq->last;
413 	if (NULL != c && MEM_CHUNK == c->type) {
414 		/* return pointer into existing buffer if large enough */
415 		size_t avail = chunk_buffer_string_space(c->mem);
416 		if (avail >= sz) {
417 			*len = avail;
418 			b = c->mem;
419 			return b->ptr + chunk_buffer_string_length(b);
420 		}
421 	}
422 
423 	/* allocate new chunk */
424 	b = chunkqueue_append_buffer_open_sz(cq, sz);
425 	*len = chunk_buffer_string_space(b);
426 	return b->ptr;
427 }
428 
429 void chunkqueue_use_memory(chunkqueue * const restrict cq, chunk *ckpt, size_t len) {
430     buffer *b = cq->last->mem;
431 
432     if (len > 0) {
433         buffer_commit(b, len);
434         cq->bytes_in += len;
435         if (cq->last == ckpt || NULL == ckpt || MEM_CHUNK != ckpt->type
436             || len > chunk_buffer_string_space(ckpt->mem)) return;
437 
438         buffer_append_string_buffer(ckpt->mem, b);
439     }
440     else if (!chunk_buffer_string_is_empty(b)) { /*(cq->last == ckpt)*/
441         return; /* last chunk is not empty */
442     }
443 
444     /* remove empty last chunk */
445     chunk_release(cq->last);
446     cq->last = ckpt;
447     *(ckpt ? &ckpt->next : &cq->first) = NULL;
448 }
449 
450 void chunkqueue_update_file(chunkqueue * const restrict cq, chunk *c, off_t len) {
451     /*assert(c->type == FILE_CHUNK);*/
452     c->file.length += len;
453     cq->bytes_in += len;
454 }
455 
456 void chunkqueue_set_tempdirs_default (const array *tempdirs, off_t upload_temp_file_size) {
457 	chunkqueue_default_tempdirs = tempdirs;
458 	chunkqueue_default_tempfile_size
459 		= (0 == upload_temp_file_size)                ? DEFAULT_TEMPFILE_SIZE
460 		: (upload_temp_file_size > MAX_TEMPFILE_SIZE) ? MAX_TEMPFILE_SIZE
461 		                                              : upload_temp_file_size;
462 }
463 
464 void chunkqueue_set_tempdirs(chunkqueue * const restrict cq, const array * const restrict tempdirs, off_t upload_temp_file_size) {
465 	force_assert(NULL != cq);
466 	cq->tempdirs = tempdirs;
467 	cq->upload_temp_file_size
468 		= (0 == upload_temp_file_size)                ? DEFAULT_TEMPFILE_SIZE
469 		: (upload_temp_file_size > MAX_TEMPFILE_SIZE) ? MAX_TEMPFILE_SIZE
470 		                                              : upload_temp_file_size;
471 	cq->tempdir_idx = 0;
472 }
473 
474 void chunkqueue_steal(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len) {
475 	while (len > 0) {
476 		chunk *c = src->first;
477 		off_t clen = 0, use;
478 
479 		if (NULL == c) break;
480 
481 		clen = chunk_remaining_length(c);
482 		if (0 == clen) {
483 			/* drop empty chunk */
484 			src->first = c->next;
485 			if (c == src->last) src->last = NULL;
486 			chunk_release(c);
487 			continue;
488 		}
489 
490 		use = len >= clen ? clen : len;
491 		len -= use;
492 
493 		if (use == clen) {
494 			/* move complete chunk */
495 			src->first = c->next;
496 			if (c == src->last) src->last = NULL;
497 
498 			chunkqueue_append_chunk(dest, c);
499 			dest->bytes_in += use;
500 		} else {
501 			/* partial chunk with length "use" */
502 
503 			switch (c->type) {
504 			case MEM_CHUNK:
505 				chunkqueue_append_mem(dest, c->mem->ptr + c->offset, use);
506 				break;
507 			case FILE_CHUNK:
508 				/* tempfile flag is in "last" chunk after the split */
509 				chunkqueue_append_file(dest, c->mem, c->file.start + c->offset, use);
510 				if (c->file.fd >= 0)
511 					dest->last->file.fd = fdevent_dup_cloexec(c->file.fd);
512 				break;
513 			}
514 
515 			c->offset += use;
516 			force_assert(0 == len);
517 		}
518 
519 		src->bytes_out += use;
520 	}
521 }
522 
523 static chunk *chunkqueue_get_append_tempfile(chunkqueue * const restrict cq, log_error_st * const restrict errh) {
524 	chunk *c;
525 	buffer *template = buffer_init_string("/var/tmp/lighttpd-upload-XXXXXX");
526 	int fd = -1;
527 
528 	if (cq->tempdirs && cq->tempdirs->used) {
529 		/* we have several tempdirs, only if all of them fail we jump out */
530 
531 		for (errno = EIO; cq->tempdir_idx < cq->tempdirs->used; ++cq->tempdir_idx) {
532 			data_string *ds = (data_string *)cq->tempdirs->data[cq->tempdir_idx];
533 
534 			buffer_copy_buffer(template, &ds->value);
535 			buffer_append_path_len(template, CONST_STR_LEN("lighttpd-upload-XXXXXX"));
536 			if (-1 != (fd = fdevent_mkstemp_append(template->ptr))) break;
537 		}
538 	} else {
539 		fd = fdevent_mkstemp_append(template->ptr);
540 	}
541 
542 	if (fd < 0) {
543 		/* (report only the last error to mkstemp()
544 		 *  if multiple temp dirs attempted) */
545 		log_perror(errh, __FILE__, __LINE__,
546 		  "opening temp-file failed: %s", template->ptr);
547 		buffer_free(template);
548 		return NULL;
549 	}
550 
551 	c = chunkqueue_append_file_chunk(cq, template, 0, 0);
552 	c->file.fd = fd;
553 	c->file.is_temp = 1;
554 
555 	buffer_free(template);
556 
557 	return c;
558 }
559 
560 int chunkqueue_append_mem_to_tempfile(chunkqueue * const restrict dest, const char * restrict mem, size_t len, log_error_st * const restrict errh) {
561 	chunk *dst_c;
562 	ssize_t written;
563 
564 	do {
565 		/*
566 		 * if the last chunk is
567 		 * - smaller than dest->upload_temp_file_size
568 		 * - not read yet (offset == 0)
569 		 * -> append to it (so it might actually become larger than dest->upload_temp_file_size)
570 		 * otherwise
571 		 * -> create a new chunk
572 		 *
573 		 * */
574 
575 		dst_c = dest->last;
576 		if (NULL != dst_c
577 			&& FILE_CHUNK == dst_c->type
578 			&& dst_c->file.is_temp
579 			&& dst_c->file.fd >= 0
580 			&& 0 == dst_c->offset) {
581 			/* ok, take the last chunk for our job */
582 
583 			if (dst_c->file.length >= (off_t)dest->upload_temp_file_size) {
584 				/* the chunk is too large now, close it */
585 				int rc = close(dst_c->file.fd);
586 				dst_c->file.fd = -1;
587 				if (0 != rc) {
588 					log_perror(errh, __FILE__, __LINE__,
589 					  "close() temp-file %s failed", dst_c->mem->ptr);
590 					return -1;
591 				}
592 				dst_c = NULL;
593 			}
594 		} else {
595 			dst_c = NULL;
596 		}
597 
598 		if (NULL == dst_c && NULL == (dst_c = chunkqueue_get_append_tempfile(dest, errh))) {
599 			return -1;
600 		}
601 	      #ifdef __COVERITY__
602 		if (dst_c->file.fd < 0) return -1;
603 	      #endif
604 
605 		/* (dst_c->file.fd >= 0) */
606 		/* coverity[negative_returns : FALSE] */
607 		written = write(dst_c->file.fd, mem, len);
608 
609 		if ((size_t) written == len) {
610 			dst_c->file.length += len;
611 			dest->bytes_in += len;
612 
613 			return 0;
614 		} else if (written >= 0) {
615 			/*(assume EINTR if partial write and retry write();
616 			 * retry write() might fail with ENOSPC if no more space on volume)*/
617 			dest->bytes_in += written;
618 			mem += written;
619 			len -= (size_t)written;
620 			dst_c->file.length += (size_t)written;
621 			/* continue; retry */
622 		} else if (errno == EINTR) {
623 			/* continue; retry */
624 		} else {
625 			int retry = (errno == ENOSPC && dest->tempdirs && ++dest->tempdir_idx < dest->tempdirs->used);
626 			if (!retry) {
627 				log_perror(errh, __FILE__, __LINE__,
628 				  "write() temp-file %s failed", dst_c->mem->ptr);
629 			}
630 
631 			if (0 == chunk_remaining_length(dst_c)) {
632 				/*(remove empty chunk and unlink tempfile)*/
633 				chunkqueue_remove_empty_chunks(dest);
634 			} else {/*(close tempfile; avoid later attempts to append)*/
635 				int rc = close(dst_c->file.fd);
636 				dst_c->file.fd = -1;
637 				if (0 != rc) {
638 					log_perror(errh, __FILE__, __LINE__,
639 					  "close() temp-file %s failed", dst_c->mem->ptr);
640 					return -1;
641 				}
642 			}
643 			if (!retry) break; /* return -1; */
644 
645 			/* continue; retry */
646 		}
647 
648 	} while (dst_c);
649 
650 	return -1;
651 }
652 
653 int chunkqueue_steal_with_tempfiles(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len, log_error_st * const restrict errh) {
654 	while (len > 0) {
655 		chunk *c = src->first;
656 		off_t clen = 0, use;
657 
658 		if (NULL == c) break;
659 
660 		clen = chunk_remaining_length(c);
661 		if (0 == clen) {
662 			/* drop empty chunk */
663 			src->first = c->next;
664 			if (c == src->last) src->last = NULL;
665 			chunk_release(c);
666 			continue;
667 		}
668 
669 		use = (len >= clen) ? clen : len;
670 		len -= use;
671 
672 		switch (c->type) {
673 		case FILE_CHUNK:
674 			if (use == clen) {
675 				/* move complete chunk */
676 				src->first = c->next;
677 				if (c == src->last) src->last = NULL;
678 				chunkqueue_append_chunk(dest, c);
679 				dest->bytes_in += use;
680 			} else {
681 				/* partial chunk with length "use" */
682 				/* tempfile flag is in "last" chunk after the split */
683 				chunkqueue_append_file(dest, c->mem, c->file.start + c->offset, use);
684 				if (c->file.fd >= 0)
685 					dest->last->file.fd = fdevent_dup_cloexec(c->file.fd);
686 
687 				c->offset += use;
688 				force_assert(0 == len);
689 			}
690 			break;
691 
692 		case MEM_CHUNK:
693 			/* store "use" bytes from memory chunk in tempfile */
694 			if (0 != chunkqueue_append_mem_to_tempfile(dest, c->mem->ptr + c->offset, use, errh)) {
695 				return -1;
696 			}
697 
698 			if (use == clen) {
699 				/* finished chunk */
700 				src->first = c->next;
701 				if (c == src->last) src->last = NULL;
702 				chunk_release(c);
703 			} else {
704 				/* partial chunk */
705 				c->offset += use;
706 				force_assert(0 == len);
707 			}
708 			break;
709 		}
710 
711 		src->bytes_out += use;
712 	}
713 
714 	return 0;
715 }
716 
717 void chunkqueue_mark_written(chunkqueue *cq, off_t len) {
718     cq->bytes_out += len;
719 
720     for (chunk *c; (c = cq->first); ) {
721         off_t c_len = chunk_remaining_length(c);
722         if (len >= c_len) { /* chunk got finished */
723             len -= c_len;
724             cq->first = c->next;
725             chunk_release(c);
726             if (0 == len) break;
727         }
728         else { /* partial chunk */
729             c->offset += len;
730             return; /* chunk not finished */
731         }
732     }
733 
734     if (NULL == cq->first)
735         cq->last = NULL;
736     else
737         chunkqueue_remove_finished_chunks(cq);
738 }
739 
740 void chunkqueue_remove_finished_chunks(chunkqueue *cq) {
741     for (chunk *c; (c = cq->first) && 0 == chunk_remaining_length(c); ){
742         if (NULL == (cq->first = c->next)) cq->last = NULL;
743         chunk_release(c);
744     }
745 }
746 
747 static void chunkqueue_remove_empty_chunks(chunkqueue *cq) {
748 	chunk *c;
749 	chunkqueue_remove_finished_chunks(cq);
750 
751 	for (c = cq->first; c && c->next; c = c->next) {
752 		if (0 == chunk_remaining_length(c->next)) {
753 			chunk *empty = c->next;
754 			c->next = empty->next;
755 			if (empty == cq->last) cq->last = c;
756 			chunk_release(empty);
757 		}
758 	}
759 }
760 
761 void chunkqueue_compact_mem(chunkqueue *cq, size_t clen) {
762     chunk *c = cq->first;
763     buffer *b = c->mem;
764     size_t len = buffer_string_length(b) - c->offset;
765     if (b->size > clen) {
766         if (0 != c->offset) {
767             memmove(b->ptr, b->ptr+c->offset, len);
768             buffer_string_set_length(b, len);
769             c->offset = 0;
770         }
771     }
772     else {
773         b = chunkqueue_prepend_buffer_open_sz(cq, clen + 8192);
774         buffer_append_string_len(b, c->mem->ptr + c->offset, len);
775         cq->first->next = c->next;
776         if (NULL == c->next) cq->last = cq->first;
777         chunk_release(c);
778         c = cq->first;
779     }
780     for (chunk *fc = c; ((clen -= len) && (c = fc->next)); ) {
781         len = buffer_string_length(c->mem) - c->offset;
782         if (len > clen) len = clen;
783         buffer_append_string_len(b, c->mem->ptr + c->offset, len);
784         fc->next = c->next;
785         if (NULL == c->next) cq->last = fc;
786         chunk_release(c);
787     }
788     /* chunkqueue_prepend_buffer_commit() is not called here;
789      * no data added/removed from chunkqueue; consolidated only */
790 }
791 
792 static int chunk_open_file_chunk(chunk * const restrict c, log_error_st * const restrict errh) {
793 	off_t offset, toSend;
794 	struct stat st;
795 
796 	force_assert(NULL != c);
797 	force_assert(FILE_CHUNK == c->type);
798 	force_assert(c->offset >= 0 && c->offset <= c->file.length);
799 
800 	offset = c->file.start + c->offset;
801 	toSend = c->file.length - c->offset;
802 
803 	if (-1 == c->file.fd) {
804 		/* (permit symlinks; should already have been checked.  However, TOC-TOU remains) */
805 		if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, 1, O_RDONLY, 0))) {
806 			log_perror(errh, __FILE__, __LINE__, "open failed: %s",c->mem->ptr);
807 			return -1;
808 		}
809 	}
810 
811 	/*(skip file size checks if file is temp file created by lighttpd)*/
812 	if (c->file.is_temp) return 0;
813 
814 	if (-1 == fstat(c->file.fd, &st)) {
815 		log_perror(errh, __FILE__, __LINE__, "fstat failed");
816 		return -1;
817 	}
818 
819 	if (offset > st.st_size || toSend > st.st_size || offset > st.st_size - toSend) {
820 		log_error(errh, __FILE__, __LINE__, "file shrunk: %s", c->mem->ptr);
821 		return -1;
822 	}
823 
824 	return 0;
825 }
826 
827 int chunkqueue_open_file_chunk(chunkqueue * const restrict cq, log_error_st * const restrict errh) {
828     return chunk_open_file_chunk(cq->first, errh);
829 }
830 
831 
832 void
833 chunkqueue_small_resp_optim (chunkqueue * const restrict cq)
834 {
835     /*(caller must verify response is small (and non-empty) before calling)*/
836 
837     /*(optimization to use fewer syscalls to send a small response by reading
838      * small files into memory, thereby avoiding use of sendfile() and multiple
839      * calls to writev()  (benefit for cleartext (non-TLS) and <= HTTP/1.1))
840      *(If TLS, then will shortly need to be in memory for encryption anyway)*/
841 
842     /*assert(cq->first);*/
843     chunk *c = cq->first;
844     chunk * const filec = c->next;
845     if (c->type != MEM_CHUNK || filec != cq->last || filec->type != FILE_CHUNK)
846         return;
847 
848     const int fd = filec->file.fd;
849     if (fd < 0) return; /*(require that file already be open)*/
850     off_t offset = filec->file.start + filec->offset;
851     if (0 != offset && -1 == lseek(fd, offset, SEEK_SET)) return;
852 
853     /* Note: there should be no size change in chunkqueue,
854      * so cq->bytes_in and cq->bytes_out should not be modified */
855 
856     buffer *b = c->mem;
857     off_t len = filec->file.length - filec->offset;
858     if ((size_t)len > chunk_buffer_string_space(b)) {
859         chunk * const nc = chunk_acquire((size_t)len+1);
860         c->next = nc;
861         nc->next = filec;
862         b = nc->mem;
863     }
864 
865     char * const ptr = b->ptr + chunk_buffer_string_length(b);
866     ssize_t rd;
867     offset = 0; /*(reuse offset var for offset into mem buffer)*/
868     do {
869         rd = read(fd, ptr+offset, len-offset);
870     } while (rd > 0 ? (offset += rd, len -= rd) : errno == EINTR);
871     /*(contents of chunkqueue kept valid even if error reading from file)*/
872     buffer_commit(b, offset);
873     filec->offset += offset;
874     chunkqueue_remove_empty_chunks(cq);
875 }
876 
877 
878 int
879 chunkqueue_peek_data (chunkqueue * const cq,
880                       char ** const data, uint32_t * const dlen,
881                       log_error_st * const errh)
882 {
883     char * const data_in = *data;
884     const uint32_t data_insz = *dlen;
885     *dlen = 0;
886 
887     for (chunk *c = cq->first; c; ) {
888         uint32_t space = data_insz - *dlen;
889         switch (c->type) {
890           case MEM_CHUNK:
891             {
892                 uint32_t have = buffer_string_length(c->mem) - (uint32_t)c->offset;
893                 if (have > space)
894                     have = space;
895                 if (*dlen)
896                     memcpy(data_in + *dlen, c->mem->ptr + c->offset, have);
897                 else
898                     *data = c->mem->ptr + c->offset; /*(reference; defer copy)*/
899                 *dlen += have;
900                 break;
901             }
902 
903           case FILE_CHUNK:
904             if (c->file.fd >= 0 || 0 == chunk_open_file_chunk(c, errh)) {
905                 off_t offset = c->file.start + c->offset;
906                 off_t toSend = c->file.length - c->offset;
907                 if (toSend > (off_t)space)
908                     toSend = (off_t)space;
909 
910                 if (-1 == lseek(c->file.fd, offset, SEEK_SET)) {
911                     log_perror(errh, __FILE__, __LINE__, "lseek");
912                     return -1;
913                 }
914                 toSend = read(c->file.fd, data_in + *dlen, (size_t)toSend);
915                 if (toSend <= 0) { /* -1 error; 0 EOF (unexpected) */
916                     log_perror(errh, __FILE__, __LINE__, "read");
917                     return -1;
918                 }
919 
920                 *dlen += (uint32_t)toSend;
921                 break;
922             }
923             return -1;
924 
925           default:
926             return -1;
927         }
928 
929         if (*dlen == data_insz)
930             break;
931 
932         c = c->next;
933         if (NULL == c)
934             break;
935 
936         if (*dlen && *data != data_in) {
937             memcpy(data_in, *data, *dlen);
938             *data = data_in;
939         }
940     }
941 
942     return 0;
943 }
944 
945 
946 int
947 chunkqueue_read_data (chunkqueue * const cq,
948                       char * const data, const uint32_t dlen,
949                       log_error_st * const errh)
950 {
951     char *ptr = data;
952     uint32_t len = dlen;
953     if (chunkqueue_peek_data(cq, &ptr, &len, errh) < 0 || len != dlen)
954         return -1;
955     if (data != ptr) memcpy(data, ptr, len);
956     chunkqueue_mark_written(cq, len);
957     return 0;
958 }
959 
960 
961 buffer *
962 chunkqueue_read_squash (chunkqueue * const restrict cq, log_error_st * const restrict errh)
963 {
964     /* read and replace chunkqueue contents with single MEM_CHUNK.
965      * cq->bytes_out is not modified */
966 
967     off_t cqlen = chunkqueue_length(cq);
968     if (cqlen >= UINT32_MAX) return NULL;
969 
970     if (cq->first && NULL == cq->first->next && cq->first->type == MEM_CHUNK)
971         return cq->first->mem;
972 
973     chunk * const c = chunk_acquire((uint32_t)cqlen+1);
974     char *data = c->mem->ptr;
975     uint32_t dlen = (uint32_t)cqlen;
976     int rc = chunkqueue_peek_data(cq, &data, &dlen, errh);
977     if (rc < 0) {
978         chunk_release(c);
979         return NULL;
980     }
981     buffer_string_set_length(c->mem, dlen);
982 
983     chunkqueue_release_chunks(cq);
984     chunkqueue_append_chunk(cq, c);
985     return c->mem;
986 }
987