xref: /lighttpd1.4/src/chunk.c (revision fb7e5a78)
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.ref = c->file.ref;
486             d->file.refchg = c->file.refchg;
487             d->file.refchg(d->file.ref, 1);
488         }
489         else
490             d->file.fd = fdevent_dup_cloexec(c->file.fd);
491     }
492 }
493 
494 void chunkqueue_steal(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len) {
495 	while (len > 0) {
496 		chunk *c = src->first;
497 		off_t clen = 0, use;
498 
499 		if (NULL == c) break;
500 
501 		clen = chunk_remaining_length(c);
502 		if (0 == clen) {
503 			/* drop empty chunk */
504 			src->first = c->next;
505 			if (c == src->last) src->last = NULL;
506 			chunk_release(c);
507 			continue;
508 		}
509 
510 		use = len >= clen ? clen : len;
511 		len -= use;
512 
513 		if (use == clen) {
514 			/* move complete chunk */
515 			src->first = c->next;
516 			if (c == src->last) src->last = NULL;
517 
518 			chunkqueue_append_chunk(dest, c);
519 			dest->bytes_in += use;
520 		} else {
521 			/* partial chunk with length "use" */
522 
523 			switch (c->type) {
524 			case MEM_CHUNK:
525 				chunkqueue_append_mem(dest, c->mem->ptr + c->offset, use);
526 				break;
527 			case FILE_CHUNK:
528 				/* tempfile flag is in "last" chunk after the split */
529 				chunkqueue_steal_partial_file_chunk(dest, c, use);
530 				break;
531 			}
532 
533 			c->offset += use;
534 			force_assert(0 == len);
535 		}
536 
537 		src->bytes_out += use;
538 	}
539 }
540 
541 static chunk *chunkqueue_get_append_tempfile(chunkqueue * const restrict cq, log_error_st * const restrict errh) {
542 	chunk *c;
543 	buffer *template = buffer_init_string("/var/tmp/lighttpd-upload-XXXXXX");
544 	int fd = -1;
545 
546 	if (cq->tempdirs && cq->tempdirs->used) {
547 		/* we have several tempdirs, only if all of them fail we jump out */
548 
549 		for (errno = EIO; cq->tempdir_idx < cq->tempdirs->used; ++cq->tempdir_idx) {
550 			data_string *ds = (data_string *)cq->tempdirs->data[cq->tempdir_idx];
551 
552 			buffer_copy_buffer(template, &ds->value);
553 			buffer_append_path_len(template, CONST_STR_LEN("lighttpd-upload-XXXXXX"));
554 			if (-1 != (fd = fdevent_mkstemp_append(template->ptr))) break;
555 		}
556 	} else {
557 		fd = fdevent_mkstemp_append(template->ptr);
558 	}
559 
560 	if (fd < 0) {
561 		/* (report only the last error to mkstemp()
562 		 *  if multiple temp dirs attempted) */
563 		log_perror(errh, __FILE__, __LINE__,
564 		  "opening temp-file failed: %s", template->ptr);
565 		buffer_free(template);
566 		return NULL;
567 	}
568 
569 	c = chunkqueue_append_file_chunk(cq, template, 0, 0);
570 	c->file.fd = fd;
571 	c->file.is_temp = 1;
572 
573 	buffer_free(template);
574 
575 	return c;
576 }
577 
578 int chunkqueue_append_mem_to_tempfile(chunkqueue * const restrict dest, const char * restrict mem, size_t len, log_error_st * const restrict errh) {
579 	chunk *dst_c;
580 	ssize_t written;
581 
582 	do {
583 		/*
584 		 * if the last chunk is
585 		 * - smaller than dest->upload_temp_file_size
586 		 * - not read yet (offset == 0)
587 		 * -> append to it (so it might actually become larger than dest->upload_temp_file_size)
588 		 * otherwise
589 		 * -> create a new chunk
590 		 *
591 		 * */
592 
593 		dst_c = dest->last;
594 		if (NULL != dst_c
595 			&& FILE_CHUNK == dst_c->type
596 			&& dst_c->file.is_temp
597 			&& dst_c->file.fd >= 0
598 			&& 0 == dst_c->offset) {
599 			/* ok, take the last chunk for our job */
600 
601 			if (dst_c->file.length >= (off_t)dest->upload_temp_file_size) {
602 				/* the chunk is too large now, close it */
603 				force_assert(0 == dst_c->file.refchg); /*(else should not happen)*/
604 				int rc = close(dst_c->file.fd);
605 				dst_c->file.fd = -1;
606 				if (0 != rc) {
607 					log_perror(errh, __FILE__, __LINE__,
608 					  "close() temp-file %s failed", dst_c->mem->ptr);
609 					return -1;
610 				}
611 				dst_c = NULL;
612 			}
613 		} else {
614 			dst_c = NULL;
615 		}
616 
617 		if (NULL == dst_c && NULL == (dst_c = chunkqueue_get_append_tempfile(dest, errh))) {
618 			return -1;
619 		}
620 	      #ifdef __COVERITY__
621 		if (dst_c->file.fd < 0) return -1;
622 	      #endif
623 
624 		/* (dst_c->file.fd >= 0) */
625 		/* coverity[negative_returns : FALSE] */
626 		written = write(dst_c->file.fd, mem, len);
627 
628 		if ((size_t) written == len) {
629 			dst_c->file.length += len;
630 			dest->bytes_in += len;
631 
632 			return 0;
633 		} else if (written >= 0) {
634 			/*(assume EINTR if partial write and retry write();
635 			 * retry write() might fail with ENOSPC if no more space on volume)*/
636 			dest->bytes_in += written;
637 			mem += written;
638 			len -= (size_t)written;
639 			dst_c->file.length += (size_t)written;
640 			/* continue; retry */
641 		} else if (errno == EINTR) {
642 			/* continue; retry */
643 		} else {
644 			int retry = (errno == ENOSPC && dest->tempdirs && ++dest->tempdir_idx < dest->tempdirs->used);
645 			if (!retry) {
646 				log_perror(errh, __FILE__, __LINE__,
647 				  "write() temp-file %s failed", dst_c->mem->ptr);
648 			}
649 
650 			if (0 == chunk_remaining_length(dst_c)) {
651 				/*(remove empty chunk and unlink tempfile)*/
652 				chunkqueue_remove_empty_chunks(dest);
653 			} else {/*(close tempfile; avoid later attempts to append)*/
654 				force_assert(0 == dst_c->file.refchg); /*(else should not happen)*/
655 				int rc = close(dst_c->file.fd);
656 				dst_c->file.fd = -1;
657 				if (0 != rc) {
658 					log_perror(errh, __FILE__, __LINE__,
659 					  "close() temp-file %s failed", dst_c->mem->ptr);
660 					return -1;
661 				}
662 			}
663 			if (!retry) break; /* return -1; */
664 
665 			/* continue; retry */
666 		}
667 
668 	} while (dst_c);
669 
670 	return -1;
671 }
672 
673 int chunkqueue_steal_with_tempfiles(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len, log_error_st * const restrict errh) {
674 	while (len > 0) {
675 		chunk *c = src->first;
676 		off_t clen = 0, use;
677 
678 		if (NULL == c) break;
679 
680 		clen = chunk_remaining_length(c);
681 		if (0 == clen) {
682 			/* drop empty chunk */
683 			src->first = c->next;
684 			if (c == src->last) src->last = NULL;
685 			chunk_release(c);
686 			continue;
687 		}
688 
689 		use = (len >= clen) ? clen : len;
690 		len -= use;
691 
692 		switch (c->type) {
693 		case FILE_CHUNK:
694 			if (use == clen) {
695 				/* move complete chunk */
696 				src->first = c->next;
697 				if (c == src->last) src->last = NULL;
698 				chunkqueue_append_chunk(dest, c);
699 				dest->bytes_in += use;
700 			} else {
701 				/* partial chunk with length "use" */
702 				/* tempfile flag is in "last" chunk after the split */
703 				chunkqueue_steal_partial_file_chunk(dest, c, use);
704 				c->offset += use;
705 				force_assert(0 == len);
706 			}
707 			break;
708 
709 		case MEM_CHUNK:
710 			/* store "use" bytes from memory chunk in tempfile */
711 			if (0 != chunkqueue_append_mem_to_tempfile(dest, c->mem->ptr + c->offset, use, errh)) {
712 				return -1;
713 			}
714 
715 			if (use == clen) {
716 				/* finished chunk */
717 				src->first = c->next;
718 				if (c == src->last) src->last = NULL;
719 				chunk_release(c);
720 			} else {
721 				/* partial chunk */
722 				c->offset += use;
723 				force_assert(0 == len);
724 			}
725 			break;
726 		}
727 
728 		src->bytes_out += use;
729 	}
730 
731 	return 0;
732 }
733 
734 void chunkqueue_mark_written(chunkqueue *cq, off_t len) {
735     cq->bytes_out += len;
736 
737     for (chunk *c; (c = cq->first); ) {
738         off_t c_len = chunk_remaining_length(c);
739         if (len >= c_len) { /* chunk got finished */
740             len -= c_len;
741             cq->first = c->next;
742             chunk_release(c);
743             if (0 == len) break;
744         }
745         else { /* partial chunk */
746             c->offset += len;
747             return; /* chunk not finished */
748         }
749     }
750 
751     if (NULL == cq->first)
752         cq->last = NULL;
753     else
754         chunkqueue_remove_finished_chunks(cq);
755 }
756 
757 void chunkqueue_remove_finished_chunks(chunkqueue *cq) {
758     for (chunk *c; (c = cq->first) && 0 == chunk_remaining_length(c); ){
759         if (NULL == (cq->first = c->next)) cq->last = NULL;
760         chunk_release(c);
761     }
762 }
763 
764 static void chunkqueue_remove_empty_chunks(chunkqueue *cq) {
765 	chunk *c;
766 	chunkqueue_remove_finished_chunks(cq);
767 
768 	for (c = cq->first; c && c->next; c = c->next) {
769 		if (0 == chunk_remaining_length(c->next)) {
770 			chunk *empty = c->next;
771 			c->next = empty->next;
772 			if (empty == cq->last) cq->last = c;
773 			chunk_release(empty);
774 		}
775 	}
776 }
777 
778 void chunkqueue_compact_mem_offset(chunkqueue * const cq) {
779     chunk * const restrict c = cq->first;
780     if (0 == c->offset) return;
781     if (c->type != MEM_CHUNK) return; /*(should not happen)*/
782 
783     buffer * const restrict b = c->mem;
784     size_t len = chunk_buffer_string_length(b) - c->offset;
785     memmove(b->ptr, b->ptr+c->offset, len);
786     c->offset = 0;
787     buffer_string_set_length(b, len);
788 }
789 
790 void chunkqueue_compact_mem(chunkqueue *cq, size_t clen) {
791     /* caller must guarantee that chunks in chunkqueue are MEM_CHUNK,
792      * which is currently always true when reading input from client */
793     chunk *c = cq->first;
794     buffer *b = c->mem;
795     size_t len = chunk_buffer_string_length(b) - c->offset;
796     if (len <= clen) return;
797     if (b->size > clen) {
798         if (chunk_buffer_string_space(b) < clen - len)
799             chunkqueue_compact_mem_offset(cq);
800     }
801     else {
802         b = chunkqueue_prepend_buffer_open_sz(cq, clen+1);
803         buffer_append_string_len(b, c->mem->ptr + c->offset, len);
804         cq->first->next = c->next;
805         if (NULL == c->next) cq->last = cq->first;
806         chunk_release(c);
807         c = cq->first;
808     }
809 
810     for (chunk *fc = c; ((clen -= len) && (c = fc->next)); ) {
811         len = chunk_buffer_string_length(c->mem) - c->offset;
812         if (len > clen) {
813             buffer_append_string_len(b, c->mem->ptr + c->offset, clen);
814             c->offset += clen;
815             break;
816         }
817         buffer_append_string_len(b, c->mem->ptr + c->offset, len);
818         fc->next = c->next;
819         if (NULL == c->next) cq->last = fc;
820         chunk_release(c);
821     }
822     /* chunkqueue_prepend_buffer_commit() is not called here;
823      * no data added/removed from chunkqueue; consolidated only */
824 }
825 
826 static int chunk_open_file_chunk(chunk * const restrict c, log_error_st * const restrict errh) {
827 	off_t offset, toSend;
828 	struct stat st;
829 
830 	force_assert(NULL != c);
831 	force_assert(FILE_CHUNK == c->type);
832 	force_assert(c->offset >= 0 && c->offset <= c->file.length);
833 
834 	offset = c->offset;
835 	toSend = c->file.length - c->offset;
836 
837 	if (-1 == c->file.fd) {
838 		/* (permit symlinks; should already have been checked.  However, TOC-TOU remains) */
839 		if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, 1, O_RDONLY, 0))) {
840 			log_perror(errh, __FILE__, __LINE__, "open failed: %s",c->mem->ptr);
841 			return -1;
842 		}
843 	}
844 
845 	/*(skip file size checks if file is temp file created by lighttpd)*/
846 	if (c->file.is_temp) return 0;
847 
848 	if (-1 == fstat(c->file.fd, &st)) {
849 		log_perror(errh, __FILE__, __LINE__, "fstat failed");
850 		return -1;
851 	}
852 
853 	if (offset > st.st_size || toSend > st.st_size || offset > st.st_size - toSend) {
854 		log_error(errh, __FILE__, __LINE__, "file shrunk: %s", c->mem->ptr);
855 		return -1;
856 	}
857 
858 	return 0;
859 }
860 
861 int chunkqueue_open_file_chunk(chunkqueue * const restrict cq, log_error_st * const restrict errh) {
862     return chunk_open_file_chunk(cq->first, errh);
863 }
864 
865 
866 void
867 chunkqueue_small_resp_optim (chunkqueue * const restrict cq)
868 {
869     /*(caller must verify response is small (and non-empty) before calling)*/
870 
871     /*(optimization to use fewer syscalls to send a small response by reading
872      * small files into memory, thereby avoiding use of sendfile() and multiple
873      * calls to writev()  (benefit for cleartext (non-TLS) and <= HTTP/1.1))
874      *(If TLS, then will shortly need to be in memory for encryption anyway)*/
875 
876     /*assert(cq->first);*/
877     chunk *c = cq->first;
878     chunk * const filec = c->next;
879     if (c->type != MEM_CHUNK || filec != cq->last || filec->type != FILE_CHUNK)
880         return;
881 
882     const int fd = filec->file.fd;
883     if (fd < 0) return; /*(require that file already be open)*/
884     off_t offset = filec->offset;
885     if (-1 == lseek(fd, offset, SEEK_SET)) return;
886 
887     /* Note: there should be no size change in chunkqueue,
888      * so cq->bytes_in and cq->bytes_out should not be modified */
889 
890     buffer *b = c->mem;
891     off_t len = filec->file.length - filec->offset;
892     if ((size_t)len > chunk_buffer_string_space(b)) {
893         chunk * const nc = chunk_acquire((size_t)len+1);
894         c->next = nc;
895         nc->next = filec;
896         b = nc->mem;
897     }
898 
899     char * const ptr = b->ptr + chunk_buffer_string_length(b);
900     ssize_t rd;
901     offset = 0; /*(reuse offset var for offset into mem buffer)*/
902     do {
903         rd = read(fd, ptr+offset, len-offset);
904     } while (rd > 0 ? (offset += rd, len -= rd) : errno == EINTR);
905     /*(contents of chunkqueue kept valid even if error reading from file)*/
906     buffer_commit(b, offset);
907     filec->offset += offset;
908     chunkqueue_remove_empty_chunks(cq);
909 }
910 
911 
912 int
913 chunkqueue_peek_data (chunkqueue * const cq,
914                       char ** const data, uint32_t * const dlen,
915                       log_error_st * const errh)
916 {
917     char * const data_in = *data;
918     const uint32_t data_insz = *dlen;
919     *dlen = 0;
920 
921     for (chunk *c = cq->first; c; ) {
922         uint32_t space = data_insz - *dlen;
923         switch (c->type) {
924           case MEM_CHUNK:
925             {
926                 uint32_t have =
927                   chunk_buffer_string_length(c->mem) - (uint32_t)c->offset;
928                 if (have > space)
929                     have = space;
930                 if (*dlen)
931                     memcpy(data_in + *dlen, c->mem->ptr + c->offset, have);
932                 else
933                     *data = c->mem->ptr + c->offset; /*(reference; defer copy)*/
934                 *dlen += have;
935                 break;
936             }
937 
938           case FILE_CHUNK:
939             if (c->file.fd >= 0 || 0 == chunk_open_file_chunk(c, errh)) {
940                 off_t offset = c->offset;
941                 off_t toSend = c->file.length - c->offset;
942                 if (toSend > (off_t)space)
943                     toSend = (off_t)space;
944 
945                 if (-1 == lseek(c->file.fd, offset, SEEK_SET)) {
946                     log_perror(errh, __FILE__, __LINE__, "lseek");
947                     return -1;
948                 }
949                 toSend = read(c->file.fd, data_in + *dlen, (size_t)toSend);
950                 if (toSend <= 0) { /* -1 error; 0 EOF (unexpected) */
951                     log_perror(errh, __FILE__, __LINE__, "read");
952                     return -1;
953                 }
954 
955                 *dlen += (uint32_t)toSend;
956                 break;
957             }
958             return -1;
959 
960           default:
961             return -1;
962         }
963 
964         if (*dlen == data_insz)
965             break;
966 
967         c = c->next;
968         if (NULL == c)
969             break;
970 
971         if (*dlen && *data != data_in) {
972             memcpy(data_in, *data, *dlen);
973             *data = data_in;
974         }
975     }
976 
977     return 0;
978 }
979 
980 
981 int
982 chunkqueue_read_data (chunkqueue * const cq,
983                       char * const data, const uint32_t dlen,
984                       log_error_st * const errh)
985 {
986     char *ptr = data;
987     uint32_t len = dlen;
988     if (chunkqueue_peek_data(cq, &ptr, &len, errh) < 0 || len != dlen)
989         return -1;
990     if (data != ptr) memcpy(data, ptr, len);
991     chunkqueue_mark_written(cq, len);
992     return 0;
993 }
994 
995 
996 buffer *
997 chunkqueue_read_squash (chunkqueue * const restrict cq, log_error_st * const restrict errh)
998 {
999     /* read and replace chunkqueue contents with single MEM_CHUNK.
1000      * cq->bytes_out is not modified */
1001 
1002     off_t cqlen = chunkqueue_length(cq);
1003     if (cqlen >= UINT32_MAX) return NULL;
1004 
1005     if (cq->first && NULL == cq->first->next && cq->first->type == MEM_CHUNK)
1006         return cq->first->mem;
1007 
1008     chunk * const c = chunk_acquire((uint32_t)cqlen+1);
1009     char *data = c->mem->ptr;
1010     uint32_t dlen = (uint32_t)cqlen;
1011     int rc = chunkqueue_peek_data(cq, &data, &dlen, errh);
1012     if (rc < 0) {
1013         chunk_release(c);
1014         return NULL;
1015     }
1016     buffer_string_set_length(c->mem, dlen);
1017 
1018     chunkqueue_release_chunks(cq);
1019     chunkqueue_append_chunk(cq, c);
1020     return c->mem;
1021 }
1022