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