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