xref: /lighttpd1.4/src/chunk.c (revision 3392e8fb)
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 int chunks_oversized_n;
32 static const array *chunkqueue_default_tempdirs = NULL;
33 static off_t chunkqueue_default_tempfile_size = DEFAULT_TEMPFILE_SIZE;
34 
35 void chunkqueue_set_chunk_size (size_t sz)
36 {
37     size_t x = 1024;
38     while (x < sz && x < (1u << 30)) x <<= 1;
39     chunk_buf_sz = sz > 0 ? x : 8192;
40 }
41 
42 void chunkqueue_set_tempdirs_default_reset (void)
43 {
44     chunk_buf_sz = 8192;
45     chunkqueue_default_tempdirs = NULL;
46     chunkqueue_default_tempfile_size = DEFAULT_TEMPFILE_SIZE;
47 }
48 
49 /* chunk buffer (c->mem) is never NULL; specialize routines from buffer.h */
50 __attribute_pure__
51 static inline size_t chunk_buffer_string_length(const buffer *b) {
52     return 0 != b->used ? b->used - 1 : 0;
53 }
54 __attribute_pure__
55 static inline int chunk_buffer_string_is_empty(const buffer *b) {
56     return b->used < 2;
57 }
58 __attribute_pure__
59 static inline size_t chunk_buffer_string_space(const buffer *b) {
60     return b->size ? b->size - (b->used | (0 == b->used)) : 0;
61 }
62 
63 chunkqueue *chunkqueue_init(chunkqueue *cq) {
64 	/* (if caller passes non-NULL cq, it must be 0-init) */
65 	if (NULL == cq) {
66 		cq = calloc(1, sizeof(*cq));
67 		force_assert(NULL != cq);
68 	}
69 
70 	cq->first = NULL;
71 	cq->last = NULL;
72 
73 	cq->tempdirs              = chunkqueue_default_tempdirs;
74 	cq->upload_temp_file_size = chunkqueue_default_tempfile_size;
75 
76 	return cq;
77 }
78 
79 __attribute_returns_nonnull__
80 static chunk *chunk_init(size_t sz) {
81 	chunk * const restrict c = calloc(1, sizeof(*c));
82 	force_assert(NULL != c);
83 
84       #if 0 /*(zeroed by calloc())*/
85 	c->type = MEM_CHUNK;
86 	c->next = NULL;
87 	c->offset = 0;
88 	c->file.length = 0;
89 	c->file.mmap.length = c->file.mmap.offset = 0;
90 	c->file.is_temp = 0;
91       #endif
92 	c->file.fd = -1;
93 	c->file.mmap.start = MAP_FAILED;
94 
95 	c->mem = buffer_init();
96 	buffer_string_prepare_copy(c->mem, sz-1);
97 
98 	return c;
99 }
100 
101 static void chunk_reset_file_chunk(chunk *c) {
102 	if (c->file.is_temp) {
103 		c->file.is_temp = 0;
104 		if (!chunk_buffer_string_is_empty(c->mem))
105 			unlink(c->mem->ptr);
106 	}
107 	if (c->file.refchg) {
108 		c->file.refchg(c->file.ref, -1);
109 		c->file.refchg = 0; /* NULL fn ptr */
110 		c->file.ref = NULL;
111 	}
112 	else if (c->file.fd != -1) {
113 		close(c->file.fd);
114 	}
115 	if (MAP_FAILED != c->file.mmap.start) {
116 		munmap(c->file.mmap.start, c->file.mmap.length);
117 		c->file.mmap.start = MAP_FAILED;
118 		c->file.mmap.length = c->file.mmap.offset = 0;
119 	}
120 	c->file.fd = -1;
121 	c->file.length = 0;
122 	c->type = MEM_CHUNK;
123 }
124 
125 static void chunk_reset(chunk *c) {
126 	if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c);
127 
128 	buffer_clear(c->mem);
129 	c->offset = 0;
130 }
131 
132 static void chunk_free(chunk *c) {
133 	if (c->type == FILE_CHUNK) chunk_reset_file_chunk(c);
134 	buffer_free(c->mem);
135 	free(c);
136 }
137 
138 static chunk * chunk_pop_oversized(size_t sz) {
139     /* future: might have buckets of certain sizes, up to socket buf sizes */
140     if (chunks_oversized && chunks_oversized->mem->size >= sz) {
141         --chunks_oversized_n;
142         chunk *c = chunks_oversized;
143         chunks_oversized = c->next;
144         return c;
145     }
146     return NULL;
147 }
148 
149 static void chunk_push_oversized(chunk * const c, const size_t sz) {
150     if (chunks_oversized_n < 64 && chunk_buf_sz >= 4096) {
151         ++chunks_oversized_n;
152         chunk **co = &chunks_oversized;
153         while (*co && sz < (*co)->mem->size) co = &(*co)->next;
154         c->next = *co;
155         *co = c;
156     }
157     else
158         chunk_free(c);
159 }
160 
161 __attribute_returns_nonnull__
162 static buffer * chunk_buffer_acquire_sz(size_t sz) {
163     chunk *c;
164     buffer *b;
165     if (sz <= chunk_buf_sz) {
166         if (chunks) {
167             c = chunks;
168             chunks = c->next;
169         }
170         else
171             c = chunk_init(chunk_buf_sz);
172             /* future: might choose to pop from chunks_oversized, if available
173              * (even if larger than sz) rather than allocating new chunk
174              * (and if doing so, might replace chunks_oversized_n) */
175     }
176     else {
177         /*(round up to nearest chunk_buf_sz)*/
178         sz = (sz + (chunk_buf_sz-1)) & ~(chunk_buf_sz-1);
179         c = chunk_pop_oversized(sz);
180         if (NULL == c)
181             c = chunk_init(sz);
182     }
183     c->next = chunk_buffers;
184     chunk_buffers = c;
185     b = c->mem;
186     c->mem = NULL;
187     return b;
188 }
189 
190 buffer * chunk_buffer_acquire(void) {
191     return chunk_buffer_acquire_sz(chunk_buf_sz);
192 }
193 
194 void chunk_buffer_release(buffer *b) {
195     if (NULL == b) return;
196     if (chunk_buffers) {
197         chunk *c = chunk_buffers;
198         chunk_buffers = c->next;
199         c->mem = b;
200         buffer_clear(b);
201         if (b->size == chunk_buf_sz) {
202             c->next = chunks;
203             chunks = c;
204         }
205         else if (b->size > chunk_buf_sz)
206             chunk_push_oversized(c, b->size);
207         else
208             chunk_free(c);
209     }
210     else {
211         buffer_free(b);
212     }
213 }
214 
215 size_t chunk_buffer_prepare_append(buffer * const b, size_t sz) {
216     if (sz > chunk_buffer_string_space(b)) {
217         sz += b->used ? b->used : 1;
218         buffer * const cb = chunk_buffer_acquire_sz(sz);
219         /* swap buffer contents and copy original b->ptr into larger b->ptr */
220         /*(this does more than buffer_move())*/
221         buffer tb = *b;
222         *b = *cb;
223         *cb = tb;
224         if ((b->used = tb.used))
225             memcpy(b->ptr, tb.ptr, tb.used);
226         chunk_buffer_release(cb);
227     }
228     return chunk_buffer_string_space(b);
229 }
230 
231 __attribute_returns_nonnull__
232 static chunk * chunk_acquire(size_t sz) {
233     if (sz <= chunk_buf_sz) {
234         if (chunks) {
235             chunk *c = chunks;
236             chunks = c->next;
237             return c;
238         }
239         sz = chunk_buf_sz;
240     }
241     else {
242         /*(round up to nearest chunk_buf_sz)*/
243         sz = (sz + (chunk_buf_sz-1)) & ~(chunk_buf_sz-1);
244         chunk *c = chunk_pop_oversized(sz);
245         if (c) return c;
246     }
247 
248     return chunk_init(sz);
249 }
250 
251 static void chunk_release(chunk *c) {
252     const size_t sz = c->mem->size;
253     if (sz == chunk_buf_sz) {
254         chunk_reset(c);
255         c->next = chunks;
256         chunks = c;
257     }
258     else if (sz > chunk_buf_sz) {
259         chunk_reset(c);
260         chunk_push_oversized(c, sz);
261     }
262     else {
263         chunk_free(c);
264     }
265 }
266 
267 void chunkqueue_chunk_pool_clear(void)
268 {
269     for (chunk *next, *c = chunks; c; c = next) {
270         next = c->next;
271         chunk_free(c);
272     }
273     chunks = NULL;
274     for (chunk *next, *c = chunks_oversized; c; c = next) {
275         next = c->next;
276         chunk_free(c);
277     }
278     chunks_oversized = NULL;
279     chunks_oversized_n = 0;
280 }
281 
282 void chunkqueue_chunk_pool_free(void)
283 {
284     chunkqueue_chunk_pool_clear();
285     for (chunk *next, *c = chunk_buffers; c; c = next) {
286         next = c->next;
287         c->mem = buffer_init(); /*(chunk_reset() expects c->mem != NULL)*/
288         chunk_free(c);
289     }
290     chunk_buffers = NULL;
291 }
292 
293 __attribute_pure__
294 static off_t chunk_remaining_length(const chunk *c) {
295     /* MEM_CHUNK or FILE_CHUNK */
296     return (c->type == MEM_CHUNK
297               ? (off_t)chunk_buffer_string_length(c->mem)
298               : c->file.length)
299            - c->offset;
300 }
301 
302 static void chunkqueue_release_chunks(chunkqueue *cq) {
303     cq->last = NULL;
304     for (chunk *c; (c = cq->first); ) {
305         cq->first = c->next;
306         chunk_release(c);
307     }
308 }
309 
310 void chunkqueue_free(chunkqueue *cq) {
311     if (NULL == cq) return;
312     chunkqueue_release_chunks(cq);
313     free(cq);
314 }
315 
316 static void chunkqueue_prepend_chunk(chunkqueue * const restrict cq, chunk * const restrict c) {
317     if (NULL == (c->next = cq->first)) cq->last = c;
318     cq->first = c;
319 }
320 
321 static void chunkqueue_append_chunk(chunkqueue * const restrict cq, chunk * const restrict c) {
322     c->next = NULL;
323     *(cq->last ? &cq->last->next : &cq->first) = c;
324     cq->last = c;
325 }
326 
327 __attribute_returns_nonnull__
328 static chunk * chunkqueue_prepend_mem_chunk(chunkqueue *cq, size_t sz) {
329     chunk *c = chunk_acquire(sz);
330     chunkqueue_prepend_chunk(cq, c);
331     return c;
332 }
333 
334 __attribute_returns_nonnull__
335 static chunk * chunkqueue_append_mem_chunk(chunkqueue *cq, size_t sz) {
336     chunk *c = chunk_acquire(sz);
337     chunkqueue_append_chunk(cq, c);
338     return c;
339 }
340 
341 __attribute_returns_nonnull__
342 static chunk * chunkqueue_append_file_chunk(chunkqueue * const restrict cq, const buffer * const restrict fn, off_t offset, off_t len) {
343     chunk *c = chunk_acquire(buffer_string_length(fn)+1);
344     chunkqueue_append_chunk(cq, c);
345     c->type = FILE_CHUNK;
346     c->offset = offset;
347     c->file.length = offset + len;
348     cq->bytes_in += len;
349     buffer_copy_buffer(c->mem, fn);
350     return c;
351 }
352 
353 void chunkqueue_reset(chunkqueue *cq) {
354     chunkqueue_release_chunks(cq);
355     cq->bytes_in = 0;
356     cq->bytes_out = 0;
357     cq->tempdir_idx = 0;
358 }
359 
360 void chunkqueue_append_file_fd(chunkqueue * const restrict cq, const buffer * const restrict fn, int fd, off_t offset, off_t len) {
361     if (len > 0) {
362         (chunkqueue_append_file_chunk(cq, fn, offset, len))->file.fd = fd;
363     }
364     else {
365         close(fd);
366     }
367 }
368 
369 void chunkqueue_append_file(chunkqueue * const restrict cq, const buffer * const restrict fn, off_t offset, off_t len) {
370     if (len > 0) {
371         chunkqueue_append_file_chunk(cq, fn, offset, len);
372     }
373 }
374 
375 
376 static int chunkqueue_append_mem_extend_chunk(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) {
377 	chunk *c = cq->last;
378 	if (0 == len) return 1;
379 	if (c != NULL && c->type == MEM_CHUNK
380 	    && chunk_buffer_string_space(c->mem) >= len) {
381 		buffer_append_string_len(c->mem, mem, len);
382 		cq->bytes_in += len;
383 		return 1;
384 	}
385 	return 0;
386 }
387 
388 
389 void chunkqueue_append_buffer(chunkqueue * const restrict cq, buffer * const restrict mem) {
390 	chunk *c;
391 	size_t len = buffer_string_length(mem);
392 	if (len < 256 && chunkqueue_append_mem_extend_chunk(cq, mem->ptr, len)) return;
393 
394 	c = chunkqueue_append_mem_chunk(cq, chunk_buf_sz);
395 	cq->bytes_in += len;
396 	buffer_move(c->mem, mem);
397 }
398 
399 
400 void chunkqueue_append_mem(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) {
401 	chunk *c;
402 	if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len))
403 		return;
404 
405 	c = chunkqueue_append_mem_chunk(cq, len+1);
406 	cq->bytes_in += len;
407 	buffer_copy_string_len(c->mem, mem, len);
408 }
409 
410 
411 void chunkqueue_append_mem_min(chunkqueue * const restrict cq, const char * const restrict mem, size_t len) {
412 	chunk *c;
413 	if (len < chunk_buf_sz && chunkqueue_append_mem_extend_chunk(cq, mem, len))
414 		return;
415 
416 	c = chunk_init(len+1);
417 	chunkqueue_append_chunk(cq, c);
418 	cq->bytes_in += len;
419 	buffer_copy_string_len(c->mem, mem, len);
420 }
421 
422 
423 void chunkqueue_append_chunkqueue(chunkqueue * const restrict cq, chunkqueue * const restrict src) {
424 	if (NULL == src->first) return;
425 
426 	if (NULL == cq->first) {
427 		cq->first = src->first;
428 	} else {
429 		cq->last->next = src->first;
430 	}
431 	cq->last = src->last;
432 	cq->bytes_in += chunkqueue_length(src);
433 
434 	src->first = NULL;
435 	src->last = NULL;
436 	src->bytes_out = src->bytes_in;
437 }
438 
439 
440 buffer * chunkqueue_prepend_buffer_open_sz(chunkqueue *cq, size_t sz) {
441 	chunk * const c = chunkqueue_prepend_mem_chunk(cq, sz);
442 	return c->mem;
443 }
444 
445 
446 buffer * chunkqueue_prepend_buffer_open(chunkqueue *cq) {
447 	return chunkqueue_prepend_buffer_open_sz(cq, chunk_buf_sz);
448 }
449 
450 
451 void chunkqueue_prepend_buffer_commit(chunkqueue *cq) {
452 	cq->bytes_in += chunk_buffer_string_length(cq->first->mem);
453 }
454 
455 
456 buffer * chunkqueue_append_buffer_open_sz(chunkqueue *cq, size_t sz) {
457 	chunk * const c = chunkqueue_append_mem_chunk(cq, sz);
458 	return c->mem;
459 }
460 
461 
462 buffer * chunkqueue_append_buffer_open(chunkqueue *cq) {
463 	return chunkqueue_append_buffer_open_sz(cq, chunk_buf_sz);
464 }
465 
466 
467 void chunkqueue_append_buffer_commit(chunkqueue *cq) {
468 	cq->bytes_in += chunk_buffer_string_length(cq->last->mem);
469 }
470 
471 
472 static void chunkqueue_remove_empty_chunks(chunkqueue *cq);
473 
474 
475 char * chunkqueue_get_memory(chunkqueue * const restrict cq, size_t * const restrict len) {
476 	size_t sz = *len ? *len : (chunk_buf_sz >> 1);
477 	buffer *b;
478 	chunk *c = cq->last;
479 	if (NULL != c && MEM_CHUNK == c->type) {
480 		/* return pointer into existing buffer if large enough */
481 		size_t avail = chunk_buffer_string_space(c->mem);
482 		if (avail >= sz) {
483 			*len = avail;
484 			b = c->mem;
485 			return b->ptr + chunk_buffer_string_length(b);
486 		}
487 	}
488 
489 	/* allocate new chunk */
490 	b = chunkqueue_append_buffer_open_sz(cq, sz);
491 	*len = chunk_buffer_string_space(b);
492 	return b->ptr;
493 }
494 
495 void chunkqueue_use_memory(chunkqueue * const restrict cq, chunk *ckpt, size_t len) {
496     buffer *b = cq->last->mem;
497 
498     if (len > 0) {
499         buffer_commit(b, len);
500         cq->bytes_in += len;
501         if (cq->last == ckpt || NULL == ckpt || MEM_CHUNK != ckpt->type
502             || len > chunk_buffer_string_space(ckpt->mem)) return;
503 
504         buffer_append_string_buffer(ckpt->mem, b);
505     }
506     else if (!chunk_buffer_string_is_empty(b)) { /*(cq->last == ckpt)*/
507         return; /* last chunk is not empty */
508     }
509 
510     /* remove empty last chunk */
511     chunk_release(cq->last);
512     cq->last = ckpt;
513     *(ckpt ? &ckpt->next : &cq->first) = NULL;
514 }
515 
516 void chunkqueue_update_file(chunkqueue * const restrict cq, chunk *c, off_t len) {
517     /*assert(c->type == FILE_CHUNK);*/
518     c->file.length += len;
519     cq->bytes_in += len;
520 }
521 
522 void chunkqueue_set_tempdirs_default (const array *tempdirs, off_t upload_temp_file_size) {
523 	chunkqueue_default_tempdirs = tempdirs;
524 	chunkqueue_default_tempfile_size
525 		= (0 == upload_temp_file_size)                ? DEFAULT_TEMPFILE_SIZE
526 		: (upload_temp_file_size > MAX_TEMPFILE_SIZE) ? MAX_TEMPFILE_SIZE
527 		                                              : upload_temp_file_size;
528 }
529 
530 void chunkqueue_set_tempdirs(chunkqueue * const restrict cq, const array * const restrict tempdirs, off_t upload_temp_file_size) {
531 	force_assert(NULL != cq);
532 	cq->tempdirs = tempdirs;
533 	cq->upload_temp_file_size
534 		= (0 == upload_temp_file_size)                ? DEFAULT_TEMPFILE_SIZE
535 		: (upload_temp_file_size > MAX_TEMPFILE_SIZE) ? MAX_TEMPFILE_SIZE
536 		                                              : upload_temp_file_size;
537 	cq->tempdir_idx = 0;
538 }
539 
540 static void chunkqueue_dup_file_chunk_fd (chunk * const restrict d, const chunk * const restrict c) {
541     /*assert(d != c);*/
542     /*assert(d->type == FILE_CHUNK);*/
543     /*assert(c->type == FILE_CHUNK);*/
544     if (c->file.fd >= 0) {
545         if (c->file.refchg) {
546             d->file.fd = c->file.fd;
547             d->file.ref = c->file.ref;
548             d->file.refchg = c->file.refchg;
549             d->file.refchg(d->file.ref, 1);
550         }
551         else
552             d->file.fd = fdevent_dup_cloexec(c->file.fd);
553     }
554 }
555 
556 static void chunkqueue_steal_partial_file_chunk(chunkqueue * const restrict dest, const chunk * const restrict c, const off_t len) {
557     chunkqueue_append_file(dest, c->mem, c->offset, len);
558     chunkqueue_dup_file_chunk_fd(dest->last, c);
559 }
560 
561 void chunkqueue_steal(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len) {
562 	while (len > 0) {
563 		chunk *c = src->first;
564 		off_t clen = 0, use;
565 
566 		if (NULL == c) break;
567 
568 		clen = chunk_remaining_length(c);
569 		if (0 == clen) {
570 			/* drop empty chunk */
571 			src->first = c->next;
572 			if (c == src->last) src->last = NULL;
573 			chunk_release(c);
574 			continue;
575 		}
576 
577 		use = len >= clen ? clen : len;
578 		len -= use;
579 
580 		if (use == clen) {
581 			/* move complete chunk */
582 			src->first = c->next;
583 			if (c == src->last) src->last = NULL;
584 
585 			chunkqueue_append_chunk(dest, c);
586 			dest->bytes_in += use;
587 		} else {
588 			/* partial chunk with length "use" */
589 
590 			switch (c->type) {
591 			case MEM_CHUNK:
592 				chunkqueue_append_mem(dest, c->mem->ptr + c->offset, use);
593 				break;
594 			case FILE_CHUNK:
595 				/* tempfile flag is in "last" chunk after the split */
596 				chunkqueue_steal_partial_file_chunk(dest, c, use);
597 				break;
598 			}
599 
600 			c->offset += use;
601 			force_assert(0 == len);
602 		}
603 
604 		src->bytes_out += use;
605 	}
606 }
607 
608 static chunk *chunkqueue_get_append_tempfile(chunkqueue * const restrict cq, log_error_st * const restrict errh) {
609 	chunk *c;
610 	buffer *template = buffer_init_string("/var/tmp/lighttpd-upload-XXXXXX");
611 	int fd = -1;
612 
613 	if (cq->tempdirs && cq->tempdirs->used) {
614 		/* we have several tempdirs, only if all of them fail we jump out */
615 
616 		for (errno = EIO; cq->tempdir_idx < cq->tempdirs->used; ++cq->tempdir_idx) {
617 			data_string *ds = (data_string *)cq->tempdirs->data[cq->tempdir_idx];
618 			buffer_copy_path_len2(template,
619 			                      CONST_BUF_LEN(&ds->value),
620 			                      CONST_STR_LEN("lighttpd-upload-XXXXXX"));
621 			if (-1 != (fd = fdevent_mkstemp_append(template->ptr))) break;
622 		}
623 	} else {
624 		fd = fdevent_mkstemp_append(template->ptr);
625 	}
626 
627 	if (fd < 0) {
628 		/* (report only the last error to mkstemp()
629 		 *  if multiple temp dirs attempted) */
630 		log_perror(errh, __FILE__, __LINE__,
631 		  "opening temp-file failed: %s", template->ptr);
632 		buffer_free(template);
633 		return NULL;
634 	}
635 
636 	c = chunkqueue_append_file_chunk(cq, template, 0, 0);
637 	c->file.fd = fd;
638 	c->file.is_temp = 1;
639 
640 	buffer_free(template);
641 
642 	return c;
643 }
644 
645 int chunkqueue_append_mem_to_tempfile(chunkqueue * const restrict dest, const char * restrict mem, size_t len, log_error_st * const restrict errh) {
646 	chunk *dst_c;
647 	ssize_t written;
648 
649 	do {
650 		/*
651 		 * if the last chunk is
652 		 * - smaller than dest->upload_temp_file_size
653 		 * - not read yet (offset == 0)
654 		 * -> append to it (so it might actually become larger than dest->upload_temp_file_size)
655 		 * otherwise
656 		 * -> create a new chunk
657 		 *
658 		 * */
659 
660 		dst_c = dest->last;
661 		if (NULL != dst_c
662 			&& FILE_CHUNK == dst_c->type
663 			&& dst_c->file.is_temp
664 			&& dst_c->file.fd >= 0
665 			&& 0 == dst_c->offset) {
666 			/* ok, take the last chunk for our job */
667 
668 			if (dst_c->file.length >= (off_t)dest->upload_temp_file_size) {
669 				/* the chunk is too large now, close it */
670 				force_assert(0 == dst_c->file.refchg); /*(else should not happen)*/
671 				int rc = close(dst_c->file.fd);
672 				dst_c->file.fd = -1;
673 				if (0 != rc) {
674 					log_perror(errh, __FILE__, __LINE__,
675 					  "close() temp-file %s failed", dst_c->mem->ptr);
676 					return -1;
677 				}
678 				dst_c = NULL;
679 			}
680 		} else {
681 			dst_c = NULL;
682 		}
683 
684 		if (NULL == dst_c && NULL == (dst_c = chunkqueue_get_append_tempfile(dest, errh))) {
685 			return -1;
686 		}
687 	      #ifdef __COVERITY__
688 		if (dst_c->file.fd < 0) return -1;
689 	      #endif
690 
691 		/* (dst_c->file.fd >= 0) */
692 		/* coverity[negative_returns : FALSE] */
693 		written = write(dst_c->file.fd, mem, len);
694 
695 		if ((size_t) written == len) {
696 			dst_c->file.length += len;
697 			dest->bytes_in += len;
698 
699 			return 0;
700 		} else if (written >= 0) {
701 			/*(assume EINTR if partial write and retry write();
702 			 * retry write() might fail with ENOSPC if no more space on volume)*/
703 			dest->bytes_in += written;
704 			mem += written;
705 			len -= (size_t)written;
706 			dst_c->file.length += (size_t)written;
707 			/* continue; retry */
708 		} else if (errno == EINTR) {
709 			/* continue; retry */
710 		} else {
711 			int retry = (errno == ENOSPC && dest->tempdirs && ++dest->tempdir_idx < dest->tempdirs->used);
712 			if (!retry) {
713 				log_perror(errh, __FILE__, __LINE__,
714 				  "write() temp-file %s failed", dst_c->mem->ptr);
715 			}
716 
717 			if (0 == chunk_remaining_length(dst_c)) {
718 				/*(remove empty chunk and unlink tempfile)*/
719 				chunkqueue_remove_empty_chunks(dest);
720 			} else {/*(close tempfile; avoid later attempts to append)*/
721 				force_assert(0 == dst_c->file.refchg); /*(else should not happen)*/
722 				int rc = close(dst_c->file.fd);
723 				dst_c->file.fd = -1;
724 				if (0 != rc) {
725 					log_perror(errh, __FILE__, __LINE__,
726 					  "close() temp-file %s failed", dst_c->mem->ptr);
727 					return -1;
728 				}
729 			}
730 			if (!retry) break; /* return -1; */
731 
732 			/* continue; retry */
733 		}
734 
735 	} while (dst_c);
736 
737 	return -1;
738 }
739 
740 int chunkqueue_steal_with_tempfiles(chunkqueue * const restrict dest, chunkqueue * const restrict src, off_t len, log_error_st * const restrict errh) {
741 	while (len > 0) {
742 		chunk *c = src->first;
743 		off_t clen = 0, use;
744 
745 		if (NULL == c) break;
746 
747 		clen = chunk_remaining_length(c);
748 		if (0 == clen) {
749 			/* drop empty chunk */
750 			src->first = c->next;
751 			if (c == src->last) src->last = NULL;
752 			chunk_release(c);
753 			continue;
754 		}
755 
756 		use = (len >= clen) ? clen : len;
757 		len -= use;
758 
759 		switch (c->type) {
760 		case FILE_CHUNK:
761 			if (use == clen) {
762 				/* move complete chunk */
763 				src->first = c->next;
764 				if (c == src->last) src->last = NULL;
765 				chunkqueue_append_chunk(dest, c);
766 				dest->bytes_in += use;
767 			} else {
768 				/* partial chunk with length "use" */
769 				/* tempfile flag is in "last" chunk after the split */
770 				chunkqueue_steal_partial_file_chunk(dest, c, use);
771 				c->offset += use;
772 				force_assert(0 == len);
773 			}
774 			break;
775 
776 		case MEM_CHUNK:
777 			/* store "use" bytes from memory chunk in tempfile */
778 			if (0 != chunkqueue_append_mem_to_tempfile(dest, c->mem->ptr + c->offset, use, errh)) {
779 				return -1;
780 			}
781 
782 			if (use == clen) {
783 				/* finished chunk */
784 				src->first = c->next;
785 				if (c == src->last) src->last = NULL;
786 				chunk_release(c);
787 			} else {
788 				/* partial chunk */
789 				c->offset += use;
790 				force_assert(0 == len);
791 			}
792 			break;
793 		}
794 
795 		src->bytes_out += use;
796 	}
797 
798 	return 0;
799 }
800 
801 void chunkqueue_append_cq_range (chunkqueue * const dst, const chunkqueue * const src, off_t offset, off_t len) {
802     /* similar to chunkqueue_steal() but copy and append src range to dst cq */
803     /* (dst cq and src cq can be the same cq, so neither is marked restrict) */
804 
805     /* copy and append range len from src to dst */
806     for (const chunk *c = src->first; len > 0 && c != NULL; c = c->next) {
807         /* scan into src to range offset (also skips empty chunks) */
808         off_t clen = chunk_remaining_length(c);
809         if (offset >= clen) {
810             offset -= clen;
811             continue;
812         }
813         clen -= offset;
814         if (len < clen) clen = len;
815         len -= clen;
816 
817         if (c->type == FILE_CHUNK) {
818             chunkqueue_append_file(dst, c->mem, c->offset + offset, clen);
819             chunkqueue_dup_file_chunk_fd(dst->last, c);
820         }
821         else { /*(c->type == MEM_CHUNK)*/
822             /*(string refs would reduce copying,
823              * but this path is not expected to be hot)*/
824             chunkqueue_append_mem(dst, c->mem->ptr + c->offset + offset, clen);
825         }
826         offset = 0;
827     }
828 }
829 
830 void chunkqueue_mark_written(chunkqueue *cq, off_t len) {
831     cq->bytes_out += len;
832 
833     for (chunk *c; (c = cq->first); ) {
834         off_t c_len = chunk_remaining_length(c);
835         if (len >= c_len) { /* chunk got finished */
836             len -= c_len;
837             cq->first = c->next;
838             chunk_release(c);
839             if (0 == len) break;
840         }
841         else { /* partial chunk */
842             c->offset += len;
843             return; /* chunk not finished */
844         }
845     }
846 
847     if (NULL == cq->first)
848         cq->last = NULL;
849     else
850         chunkqueue_remove_finished_chunks(cq);
851 }
852 
853 void chunkqueue_remove_finished_chunks(chunkqueue *cq) {
854     for (chunk *c; (c = cq->first) && 0 == chunk_remaining_length(c); ){
855         if (NULL == (cq->first = c->next)) cq->last = NULL;
856         chunk_release(c);
857     }
858 }
859 
860 static void chunkqueue_remove_empty_chunks(chunkqueue *cq) {
861 	chunk *c;
862 	chunkqueue_remove_finished_chunks(cq);
863 
864 	for (c = cq->first; c && c->next; c = c->next) {
865 		if (0 == chunk_remaining_length(c->next)) {
866 			chunk *empty = c->next;
867 			c->next = empty->next;
868 			if (empty == cq->last) cq->last = c;
869 			chunk_release(empty);
870 		}
871 	}
872 }
873 
874 void chunkqueue_compact_mem_offset(chunkqueue * const cq) {
875     chunk * const restrict c = cq->first;
876     if (0 == c->offset) return;
877     if (c->type != MEM_CHUNK) return; /*(should not happen)*/
878 
879     buffer * const restrict b = c->mem;
880     size_t len = chunk_buffer_string_length(b) - c->offset;
881     memmove(b->ptr, b->ptr+c->offset, len);
882     c->offset = 0;
883     buffer_string_set_length(b, len);
884 }
885 
886 void chunkqueue_compact_mem(chunkqueue *cq, size_t clen) {
887     /* caller must guarantee that chunks in chunkqueue are MEM_CHUNK,
888      * which is currently always true when reading input from client */
889     chunk *c = cq->first;
890     buffer *b = c->mem;
891     size_t len = chunk_buffer_string_length(b) - c->offset;
892     if (len >= clen) return;
893     if (b->size > clen) {
894         if (chunk_buffer_string_space(b) < clen - len)
895             chunkqueue_compact_mem_offset(cq);
896     }
897     else {
898         b = chunkqueue_prepend_buffer_open_sz(cq, clen+1);
899         buffer_append_string_len(b, c->mem->ptr + c->offset, len);
900         cq->first->next = c->next;
901         if (NULL == c->next) cq->last = cq->first;
902         chunk_release(c);
903         c = cq->first;
904     }
905 
906     for (chunk *fc = c; ((clen -= len) && (c = fc->next)); ) {
907         len = chunk_buffer_string_length(c->mem) - c->offset;
908         if (len > clen) {
909             buffer_append_string_len(b, c->mem->ptr + c->offset, clen);
910             c->offset += clen;
911             break;
912         }
913         buffer_append_string_len(b, c->mem->ptr + c->offset, len);
914         fc->next = c->next;
915         if (NULL == c->next) cq->last = fc;
916         chunk_release(c);
917     }
918     /* chunkqueue_prepend_buffer_commit() is not called here;
919      * no data added/removed from chunkqueue; consolidated only */
920 }
921 
922 static int chunk_open_file_chunk(chunk * const restrict c, log_error_st * const restrict errh) {
923 	if (-1 == c->file.fd) {
924 		/* (permit symlinks; should already have been checked.  However, TOC-TOU remains) */
925 		if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, 1, O_RDONLY, 0))) {
926 			log_perror(errh, __FILE__, __LINE__, "open failed: %s",c->mem->ptr);
927 			return -1;
928 		}
929 	}
930 
931 	/*(skip file size checks if file is temp file created by lighttpd)*/
932 	if (c->file.is_temp) return 0;
933 
934 	force_assert(FILE_CHUNK == c->type);
935 	force_assert(c->offset >= 0 && c->offset <= c->file.length);
936 
937 	struct stat st;
938 	if (-1 == fstat(c->file.fd, &st)) {
939 		log_perror(errh, __FILE__, __LINE__, "fstat failed");
940 		return -1;
941 	}
942 
943 	const off_t offset = c->offset;
944 	const off_t len = c->file.length - c->offset;
945 	if (offset > st.st_size || len > st.st_size || offset > st.st_size - len) {
946 		log_error(errh, __FILE__, __LINE__, "file shrunk: %s", c->mem->ptr);
947 		return -1;
948 	}
949 
950 	return 0;
951 }
952 
953 int chunkqueue_open_file_chunk(chunkqueue * const restrict cq, log_error_st * const restrict errh) {
954     return chunk_open_file_chunk(cq->first, errh);
955 }
956 
957 
958 #if defined(HAVE_MMAP) || defined(_WIN32) /*(see local sys-mmap.h)*/
959 __attribute_cold__
960 #endif
961 __attribute_noinline__
962 static ssize_t
963 chunkqueue_write_chunk_file_intermed (const int fd, chunk * const restrict c, log_error_st * const errh)
964 {
965     char buf[16384];
966     char *data = buf;
967     const off_t count = c->file.length - c->offset;
968     uint32_t dlen = count < (off_t)sizeof(buf) ? (uint32_t)count : sizeof(buf);
969     chunkqueue cq = {c,c,0,0,0,0,0}; /*(fake cq for chunkqueue_peek_data())*/
970     if (0 != chunkqueue_peek_data(&cq, &data, &dlen, errh) && 0 == dlen)
971         return -1;
972     ssize_t wr;
973     do { wr = write(fd, data, dlen); } while (-1 == wr && errno == EINTR);
974     return wr;
975 }
976 
977 
978 #if defined(HAVE_MMAP) || defined(_WIN32) /*(see local sys-mmap.h)*/
979 /*(improved from network_write_mmap.c)*/
980 static off_t
981 mmap_align_offset (off_t start)
982 {
983     static off_t pagemask = 0;
984     if (0 == pagemask) {
985       #ifndef _WIN32
986         long pagesize = sysconf(_SC_PAGESIZE);
987       #else
988         long pagesize = -1; /*(not implemented (yet))*/
989       #endif
990         if (-1 == pagesize) pagesize = 4096;
991         pagemask = ~((off_t)pagesize - 1); /* pagesize always power-of-2 */
992     }
993     return (start & pagemask);
994 }
995 #endif
996 
997 
998 #if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE \
999  && (!defined _LARGEFILE_SOURCE || defined HAVE_SENDFILE64) \
1000  && defined(__linux__) && !defined HAVE_SENDFILE_BROKEN
1001 #include <sys/sendfile.h>
1002 #include <stdint.h>
1003 #endif
1004 static ssize_t
1005 chunkqueue_write_chunk_file (const int fd, chunk * const restrict c, log_error_st * const errh)
1006 {
1007     /*(similar to network_write_file_chunk_mmap(), but does not use send() on
1008     *  Windows because fd is expected to be file or pipe here, not socket)*/
1009 
1010     if (0 != chunk_open_file_chunk(c, errh))
1011         return -1;
1012 
1013     const off_t count = c->file.length - c->offset;
1014     if (0 == count) return 0; /*(sanity check)*/
1015 
1016     ssize_t wr;
1017   #if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE \
1018    && (!defined _LARGEFILE_SOURCE || defined HAVE_SENDFILE64) \
1019    && defined(__linux__) && !defined HAVE_SENDFILE_BROKEN
1020     /* Linux kernel >= 2.6.33 supports sendfile() between most fd types */
1021     off_t offset = c->offset;
1022     wr = sendfile(fd, c->file.fd, &offset, count<INT32_MAX ? count : INT32_MAX);
1023     if (wr >= 0) return wr;
1024 
1025     if (wr < 0 && (errno == EINVAL || errno == ENOSYS))
1026   #endif
1027     {
1028       #if defined(HAVE_MMAP) || defined(_WIN32) /*(see local sys-mmap.h)*/
1029         /*(caller is responsible for handling SIGBUS if chunkqueue might contain
1030          * untrusted file, i.e. any file other than lighttpd-created tempfile)*/
1031         /*(tempfiles are expected for input, MAP_PRIVATE used for portability)*/
1032         /*(mmaps and writes complete chunk instead of only small parts; files
1033          * are expected to be temp files with reasonable chunk sizes)*/
1034 
1035         /* (re)mmap the buffer if range is not covered completely */
1036         if (MAP_FAILED == c->file.mmap.start
1037             || c->offset < c->file.mmap.offset
1038             || c->file.length
1039                  > (off_t)(c->file.mmap.offset + c->file.mmap.length)) {
1040 
1041             if (MAP_FAILED != c->file.mmap.start) {
1042                 munmap(c->file.mmap.start, c->file.mmap.length);
1043                 c->file.mmap.start = MAP_FAILED;
1044             }
1045 
1046             c->file.mmap.offset = mmap_align_offset(c->offset);
1047             c->file.mmap.length = c->file.length - c->file.mmap.offset;
1048             c->file.mmap.start  =
1049               mmap(NULL, c->file.mmap.length, PROT_READ, MAP_PRIVATE,
1050                    c->file.fd, c->file.mmap.offset);
1051 
1052           #if 0
1053             /* close() fd as soon as fully mmap() rather than when done w/ chunk
1054              * (possibly worthwhile to keep active fd count lower) */
1055             if (c->file.is_temp && !c->file.refchg) {
1056                 close(c->file.fd);
1057                 c->file.fd = -1;
1058             }
1059           #endif
1060         }
1061 
1062         if (MAP_FAILED != c->file.mmap.start) {
1063             const char * const data =
1064               c->file.mmap.start + c->offset - c->file.mmap.offset;
1065             do { wr = write(fd,data,count); } while (-1 == wr && errno==EINTR);
1066         }
1067         else
1068       #endif
1069             wr = chunkqueue_write_chunk_file_intermed(fd, c, errh);
1070     }
1071     return wr;
1072 }
1073 
1074 
1075 static ssize_t
1076 chunkqueue_write_chunk_mem (const int fd, const chunk * const restrict c)
1077 {
1078     const void * const buf = c->mem->ptr + c->offset;
1079     const size_t count = chunk_buffer_string_length(c->mem) - (size_t)c->offset;
1080     ssize_t wr;
1081     do { wr = write(fd, buf, count); } while (-1 == wr && errno == EINTR);
1082     return wr;
1083 }
1084 
1085 
1086 ssize_t
1087 chunkqueue_write_chunk (const int fd, chunkqueue * const restrict cq, log_error_st * const restrict errh)
1088 {
1089     /*(note: expects non-empty cq->first)*/
1090     chunk * const c = cq->first;
1091     switch (c->type) {
1092       case MEM_CHUNK:
1093         return chunkqueue_write_chunk_mem(fd, c);
1094       case FILE_CHUNK:
1095         return chunkqueue_write_chunk_file(fd, c, errh);
1096       default:
1097         errno = EINVAL;
1098         return -1;
1099     }
1100 }
1101 
1102 
1103 ssize_t
1104 chunkqueue_write_chunk_to_pipe (const int fd, chunkqueue * const restrict cq, log_error_st * const restrict errh)
1105 {
1106     /*(note: expects non-empty cq->first)*/
1107   #ifdef SPLICE_F_NONBLOCK /* splice() temp files to pipe on Linux */
1108     chunk * const c = cq->first;
1109     if (c->type == FILE_CHUNK) {
1110         loff_t abs_offset = c->offset;
1111         return (0 == chunk_open_file_chunk(c, errh))
1112           ? splice(c->file.fd, &abs_offset, fd, NULL,
1113                    (size_t)(c->file.length - c->offset), SPLICE_F_NONBLOCK)
1114           : -1;
1115     }
1116   #endif
1117     return chunkqueue_write_chunk(fd, cq, errh);
1118 }
1119 
1120 
1121 void
1122 chunkqueue_small_resp_optim (chunkqueue * const restrict cq)
1123 {
1124     /*(caller must verify response is small (and non-empty) before calling)*/
1125 
1126     /*(optimization to use fewer syscalls to send a small response by reading
1127      * small files into memory, thereby avoiding use of sendfile() and multiple
1128      * calls to writev()  (benefit for cleartext (non-TLS) and <= HTTP/1.1))
1129      *(If TLS, then will shortly need to be in memory for encryption anyway)*/
1130 
1131     /*assert(cq->first);*/
1132     chunk *c = cq->first;
1133     chunk * const filec = c->next;
1134     if (c->type != MEM_CHUNK || filec != cq->last || filec->type != FILE_CHUNK)
1135         return;
1136 
1137     const int fd = filec->file.fd;
1138     if (fd < 0) return; /*(require that file already be open)*/
1139     off_t offset = filec->offset;
1140     if (-1 == lseek(fd, offset, SEEK_SET)) return;
1141 
1142     /* Note: there should be no size change in chunkqueue,
1143      * so cq->bytes_in and cq->bytes_out should not be modified */
1144 
1145     buffer *b = c->mem;
1146     off_t len = filec->file.length - filec->offset;
1147     if ((size_t)len > chunk_buffer_string_space(b)) {
1148         chunk * const nc = chunk_acquire((size_t)len+1);
1149         c->next = nc;
1150         nc->next = filec;
1151         b = nc->mem;
1152     }
1153 
1154     char * const ptr = b->ptr + chunk_buffer_string_length(b);
1155     ssize_t rd;
1156     offset = 0; /*(reuse offset var for offset into mem buffer)*/
1157     do {
1158         rd = read(fd, ptr+offset, (size_t)len);
1159     } while (rd > 0 ? (offset += rd, len -= rd) : errno == EINTR);
1160     /*(contents of chunkqueue kept valid even if error reading from file)*/
1161     if (len)
1162         cq->bytes_in -= len;
1163     buffer_commit(b, offset);
1164     filec->offset += offset;
1165     chunkqueue_remove_empty_chunks(cq);
1166 }
1167 
1168 
1169 int
1170 chunkqueue_peek_data (chunkqueue * const cq,
1171                       char ** const data, uint32_t * const dlen,
1172                       log_error_st * const errh)
1173 {
1174     char * const data_in = *data;
1175     const uint32_t data_insz = *dlen;
1176     *dlen = 0;
1177 
1178     for (chunk *c = cq->first; c; ) {
1179         uint32_t space = data_insz - *dlen;
1180         switch (c->type) {
1181           case MEM_CHUNK:
1182             {
1183                 uint32_t have =
1184                   chunk_buffer_string_length(c->mem) - (uint32_t)c->offset;
1185                 if (have > space)
1186                     have = space;
1187                 if (*dlen)
1188                     memcpy(data_in + *dlen, c->mem->ptr + c->offset, have);
1189                 else
1190                     *data = c->mem->ptr + c->offset; /*(reference; defer copy)*/
1191                 *dlen += have;
1192                 break;
1193             }
1194 
1195           case FILE_CHUNK:
1196             if (c->file.fd >= 0 || 0 == chunk_open_file_chunk(c, errh)) {
1197                 off_t offset = c->offset;
1198                 off_t len = c->file.length - c->offset;
1199                 if (len > (off_t)space)
1200                     len = (off_t)space;
1201 
1202                 if (-1 == lseek(c->file.fd, offset, SEEK_SET)) {
1203                     log_perror(errh, __FILE__, __LINE__, "lseek(\"%s\")",
1204                                c->mem->ptr);
1205                     return -1;
1206                 }
1207                 ssize_t rd;
1208                 do {
1209                     rd = read(c->file.fd, data_in + *dlen, (size_t)len);
1210                 } while (-1 == rd && errno == EINTR);
1211                 if (rd <= 0) { /* -1 error; 0 EOF (unexpected) */
1212                     log_perror(errh, __FILE__, __LINE__, "read(\"%s\")",
1213                                c->mem->ptr);
1214                     return -1;
1215                 }
1216 
1217                 *dlen += (uint32_t)rd;
1218                 break;
1219             }
1220             return -1;
1221 
1222           default:
1223             return -1;
1224         }
1225 
1226         if (*dlen == data_insz)
1227             break;
1228 
1229         c = c->next;
1230         if (NULL == c)
1231             break;
1232 
1233         if (*dlen && *data != data_in) {
1234             memcpy(data_in, *data, *dlen);
1235             *data = data_in;
1236         }
1237     }
1238 
1239     return 0;
1240 }
1241 
1242 
1243 int
1244 chunkqueue_read_data (chunkqueue * const cq,
1245                       char * const data, const uint32_t dlen,
1246                       log_error_st * const errh)
1247 {
1248     char *ptr = data;
1249     uint32_t len = dlen;
1250     if (chunkqueue_peek_data(cq, &ptr, &len, errh) < 0 || len != dlen)
1251         return -1;
1252     if (data != ptr) memcpy(data, ptr, len);
1253     chunkqueue_mark_written(cq, len);
1254     return 0;
1255 }
1256 
1257 
1258 buffer *
1259 chunkqueue_read_squash (chunkqueue * const restrict cq, log_error_st * const restrict errh)
1260 {
1261     /* read and replace chunkqueue contents with single MEM_CHUNK.
1262      * cq->bytes_out is not modified */
1263 
1264     off_t cqlen = chunkqueue_length(cq);
1265     if (cqlen >= UINT32_MAX) return NULL;
1266 
1267     if (cq->first && NULL == cq->first->next && cq->first->type == MEM_CHUNK)
1268         return cq->first->mem;
1269 
1270     chunk * const c = chunk_acquire((uint32_t)cqlen+1);
1271     char *data = c->mem->ptr;
1272     uint32_t dlen = (uint32_t)cqlen;
1273     int rc = chunkqueue_peek_data(cq, &data, &dlen, errh);
1274     if (rc < 0) {
1275         chunk_release(c);
1276         return NULL;
1277     }
1278     buffer_string_set_length(c->mem, dlen);
1279 
1280     chunkqueue_release_chunks(cq);
1281     chunkqueue_append_chunk(cq, c);
1282     return c->mem;
1283 }
1284