4 * the network chunk-API
14 #include <sys/types.h>
25 /* default 1MB, upper limit 128MB */
26 #define DEFAULT_TEMPFILE_SIZE (1 * 1024 * 1024)
27 #define MAX_TEMPFILE_SIZE (128 * 1024 * 1024)
29 static array
*chunkqueue_default_tempdirs
= NULL
;
30 static unsigned int chunkqueue_default_tempfile_size
= DEFAULT_TEMPFILE_SIZE
;
32 void chunkqueue_set_tempdirs_default_reset (void)
34 chunkqueue_default_tempdirs
= NULL
;
35 chunkqueue_default_tempfile_size
= DEFAULT_TEMPFILE_SIZE
;
38 chunkqueue
*chunkqueue_init(void) {
41 cq
= calloc(1, sizeof(*cq
));
42 force_assert(NULL
!= cq
);
49 cq
->tempdirs
= chunkqueue_default_tempdirs
;
50 cq
->upload_temp_file_size
= chunkqueue_default_tempfile_size
;
55 static chunk
*chunk_init(void) {
58 c
= calloc(1, sizeof(*c
));
59 force_assert(NULL
!= c
);
62 c
->mem
= buffer_init();
63 c
->file
.name
= buffer_init();
64 c
->file
.start
= c
->file
.length
= c
->file
.mmap
.offset
= 0;
66 c
->file
.mmap
.start
= MAP_FAILED
;
67 c
->file
.mmap
.length
= 0;
75 static void chunk_reset(chunk
*c
) {
76 if (NULL
== c
) return;
82 if (c
->file
.is_temp
&& !buffer_string_is_empty(c
->file
.name
)) {
83 unlink(c
->file
.name
->ptr
);
86 buffer_reset(c
->file
.name
);
88 if (c
->file
.fd
!= -1) {
92 if (MAP_FAILED
!= c
->file
.mmap
.start
) {
93 munmap(c
->file
.mmap
.start
, c
->file
.mmap
.length
);
94 c
->file
.mmap
.start
= MAP_FAILED
;
96 c
->file
.start
= c
->file
.length
= c
->file
.mmap
.offset
= 0;
97 c
->file
.mmap
.length
= 0;
103 static void chunk_free(chunk
*c
) {
104 if (NULL
== c
) return;
109 buffer_free(c
->file
.name
);
114 static off_t
chunk_remaining_length(const chunk
*c
) {
118 len
= buffer_string_length(c
->mem
);
121 len
= c
->file
.length
;
124 force_assert(c
->type
== MEM_CHUNK
|| c
->type
== FILE_CHUNK
);
127 force_assert(c
->offset
<= len
);
128 return len
- c
->offset
;
131 void chunkqueue_free(chunkqueue
*cq
) {
134 if (NULL
== cq
) return;
136 for (c
= cq
->first
; c
; ) {
142 for (c
= cq
->unused
; c
; ) {
151 static void chunkqueue_push_unused_chunk(chunkqueue
*cq
, chunk
*c
) {
152 force_assert(NULL
!= cq
&& NULL
!= c
);
154 /* keep at max 4 chunks in the 'unused'-cache */
155 if (cq
->unused_chunks
> 4) {
159 c
->next
= cq
->unused
;
165 static chunk
*chunkqueue_get_unused_chunk(chunkqueue
*cq
) {
168 force_assert(NULL
!= cq
);
170 /* check if we have a unused chunk */
171 if (0 == cq
->unused
) {
174 /* take the first element from the list (a stack) */
176 cq
->unused
= c
->next
;
184 static void chunkqueue_prepend_chunk(chunkqueue
*cq
, chunk
*c
) {
188 if (NULL
== cq
->last
) {
191 cq
->bytes_in
+= chunk_remaining_length(c
);
194 static void chunkqueue_append_chunk(chunkqueue
*cq
, chunk
*c
) {
201 if (NULL
== cq
->first
) {
204 cq
->bytes_in
+= chunk_remaining_length(c
);
207 void chunkqueue_reset(chunkqueue
*cq
) {
208 chunk
*cur
= cq
->first
;
210 cq
->first
= cq
->last
= NULL
;
212 while (NULL
!= cur
) {
213 chunk
*next
= cur
->next
;
214 chunkqueue_push_unused_chunk(cq
, cur
);
223 void chunkqueue_append_file_fd(chunkqueue
*cq
, buffer
*fn
, int fd
, off_t offset
, off_t len
) {
231 c
= chunkqueue_get_unused_chunk(cq
);
233 c
->type
= FILE_CHUNK
;
235 buffer_copy_buffer(c
->file
.name
, fn
);
236 c
->file
.start
= offset
;
237 c
->file
.length
= len
;
241 chunkqueue_append_chunk(cq
, c
);
244 void chunkqueue_append_file(chunkqueue
*cq
, buffer
*fn
, off_t offset
, off_t len
) {
247 if (0 == len
) return;
249 c
= chunkqueue_get_unused_chunk(cq
);
251 c
->type
= FILE_CHUNK
;
253 buffer_copy_buffer(c
->file
.name
, fn
);
254 c
->file
.start
= offset
;
255 c
->file
.length
= len
;
258 chunkqueue_append_chunk(cq
, c
);
261 void chunkqueue_append_buffer(chunkqueue
*cq
, buffer
*mem
) {
264 if (buffer_string_is_empty(mem
)) return;
266 c
= chunkqueue_get_unused_chunk(cq
);
268 force_assert(NULL
!= c
->mem
);
269 buffer_move(c
->mem
, mem
);
271 chunkqueue_append_chunk(cq
, c
);
274 void chunkqueue_prepend_buffer(chunkqueue
*cq
, buffer
*mem
) {
277 if (buffer_string_is_empty(mem
)) return;
279 c
= chunkqueue_get_unused_chunk(cq
);
281 force_assert(NULL
!= c
->mem
);
282 buffer_move(c
->mem
, mem
);
284 chunkqueue_prepend_chunk(cq
, c
);
288 void chunkqueue_append_mem(chunkqueue
*cq
, const char * mem
, size_t len
) {
291 if (0 == len
) return;
293 c
= chunkqueue_get_unused_chunk(cq
);
295 buffer_copy_string_len(c
->mem
, mem
, len
);
297 chunkqueue_append_chunk(cq
, c
);
301 void chunkqueue_append_chunkqueue(chunkqueue
*cq
, chunkqueue
*src
) {
302 if (src
== NULL
|| NULL
== src
->first
) return;
304 if (NULL
== cq
->first
) {
305 cq
->first
= src
->first
;
307 cq
->last
->next
= src
->first
;
309 cq
->last
= src
->last
;
310 cq
->bytes_in
+= (src
->bytes_in
- src
->bytes_out
);
314 src
->bytes_out
= src
->bytes_in
;
318 void chunkqueue_get_memory(chunkqueue
*cq
, char **mem
, size_t *len
, size_t min_size
, size_t alloc_size
) {
319 static const size_t REALLOC_MAX_SIZE
= 256;
325 force_assert(NULL
!= cq
);
326 if (NULL
== mem
) mem
= &dummy_mem
;
327 if (NULL
== len
) len
= &dummy_len
;
329 /* default values: */
330 if (0 == min_size
) min_size
= 1024;
331 if (0 == alloc_size
) alloc_size
= 4096;
332 if (alloc_size
< min_size
) alloc_size
= min_size
;
334 if (NULL
!= cq
->last
&& MEM_CHUNK
== cq
->last
->type
) {
338 have
= buffer_string_space(b
);
340 /* unused buffer: allocate space */
341 if (buffer_string_is_empty(b
)) {
342 buffer_string_prepare_copy(b
, alloc_size
);
343 have
= buffer_string_space(b
);
345 /* if buffer is really small just make it bigger */
346 else if (have
< min_size
&& b
->size
<= REALLOC_MAX_SIZE
) {
347 size_t cur_len
= buffer_string_length(b
);
348 size_t new_size
= cur_len
+ min_size
, append
;
349 if (new_size
< alloc_size
) new_size
= alloc_size
;
351 append
= new_size
- cur_len
;
352 if (append
>= min_size
) {
353 buffer_string_prepare_append(b
, append
);
354 have
= buffer_string_space(b
);
358 /* return pointer into existing buffer if large enough */
359 if (have
>= min_size
) {
360 *mem
= b
->ptr
+ buffer_string_length(b
);
366 /* allocate new chunk */
367 c
= chunkqueue_get_unused_chunk(cq
);
369 chunkqueue_append_chunk(cq
, c
);
372 buffer_string_prepare_append(b
, alloc_size
);
374 *mem
= b
->ptr
+ buffer_string_length(b
);
375 *len
= buffer_string_space(b
);
378 void chunkqueue_use_memory(chunkqueue
*cq
, size_t len
) {
381 force_assert(NULL
!= cq
);
382 force_assert(NULL
!= cq
->last
&& MEM_CHUNK
== cq
->last
->type
);
386 buffer_commit(b
, len
);
388 } else if (buffer_string_is_empty(b
)) {
389 /* unused buffer: can't remove chunk easily from
390 * end of list, so just reset the buffer
396 void chunkqueue_set_tempdirs_default (array
*tempdirs
, unsigned int upload_temp_file_size
) {
397 chunkqueue_default_tempdirs
= tempdirs
;
398 chunkqueue_default_tempfile_size
399 = (0 == upload_temp_file_size
) ? DEFAULT_TEMPFILE_SIZE
400 : (upload_temp_file_size
> MAX_TEMPFILE_SIZE
) ? MAX_TEMPFILE_SIZE
401 : upload_temp_file_size
;
405 void chunkqueue_set_tempdirs(chunkqueue
*cq
, array
*tempdirs
, unsigned int upload_temp_file_size
) {
406 force_assert(NULL
!= cq
);
407 cq
->tempdirs
= tempdirs
;
408 cq
->upload_temp_file_size
409 = (0 == upload_temp_file_size
) ? DEFAULT_TEMPFILE_SIZE
410 : (upload_temp_file_size
> MAX_TEMPFILE_SIZE
) ? MAX_TEMPFILE_SIZE
411 : upload_temp_file_size
;
416 void chunkqueue_steal(chunkqueue
*dest
, chunkqueue
*src
, off_t len
) {
418 chunk
*c
= src
->first
;
421 if (NULL
== c
) break;
423 clen
= chunk_remaining_length(c
);
425 /* drop empty chunk */
426 src
->first
= c
->next
;
427 if (c
== src
->last
) src
->last
= NULL
;
428 chunkqueue_push_unused_chunk(src
, c
);
432 use
= len
>= clen
? clen
: len
;
436 /* move complete chunk */
437 src
->first
= c
->next
;
438 if (c
== src
->last
) src
->last
= NULL
;
440 chunkqueue_append_chunk(dest
, c
);
442 /* partial chunk with length "use" */
446 chunkqueue_append_mem(dest
, c
->mem
->ptr
+ c
->offset
, use
);
449 /* tempfile flag is in "last" chunk after the split */
450 chunkqueue_append_file(dest
, c
->file
.name
, c
->file
.start
+ c
->offset
, use
);
455 force_assert(0 == len
);
458 src
->bytes_out
+= use
;
462 static chunk
*chunkqueue_get_append_tempfile(server
*srv
, chunkqueue
*cq
) {
464 buffer
*template = buffer_init_string("/var/tmp/lighttpd-upload-XXXXXX");
467 if (cq
->tempdirs
&& cq
->tempdirs
->used
) {
468 /* we have several tempdirs, only if all of them fail we jump out */
470 for (errno
= EIO
; cq
->tempdir_idx
< cq
->tempdirs
->used
; ++cq
->tempdir_idx
) {
471 data_string
*ds
= (data_string
*)cq
->tempdirs
->data
[cq
->tempdir_idx
];
473 buffer_copy_buffer(template, ds
->value
);
474 buffer_append_slash(template);
475 buffer_append_string_len(template, CONST_STR_LEN("lighttpd-upload-XXXXXX"));
478 /* POSIX-2008 requires mkstemp create file with 0600 perms */
481 /* coverity[secure_temp : FALSE] */
482 if (-1 != (fd
= mkstemp(template->ptr
))) break;
486 /* POSIX-2008 requires mkstemp create file with 0600 perms */
489 /* coverity[secure_temp : FALSE] */
490 fd
= mkstemp(template->ptr
);
494 /* (report only the last error to mkstemp()
495 * if multiple temp dirs attempted) */
496 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
497 "opening temp-file failed:",
498 template, strerror(errno
));
499 buffer_free(template);
503 if (0 != fcntl(fd
, F_SETFL
, fcntl(fd
, F_GETFL
, 0) | O_APPEND
)) {
504 /* (should not happen; fd is regular file) */
505 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
506 "fcntl():", template, strerror(errno
));
508 buffer_free(template);
511 fdevent_setfd_cloexec(fd
);
513 c
= chunkqueue_get_unused_chunk(cq
);
514 c
->type
= FILE_CHUNK
;
517 buffer_copy_buffer(c
->file
.name
, template);
520 chunkqueue_append_chunk(cq
, c
);
522 buffer_free(template);
527 static void chunkqueue_remove_empty_chunks(chunkqueue
*cq
);
529 int chunkqueue_append_mem_to_tempfile(server
*srv
, chunkqueue
*dest
, const char *mem
, size_t len
) {
535 * if the last chunk is
536 * - smaller than dest->upload_temp_file_size
537 * - not read yet (offset == 0)
538 * -> append to it (so it might actually become larger than dest->upload_temp_file_size)
540 * -> create a new chunk
546 && FILE_CHUNK
== dst_c
->type
547 && dst_c
->file
.is_temp
548 && dst_c
->file
.fd
>= 0
549 && 0 == dst_c
->offset
) {
550 /* ok, take the last chunk for our job */
552 if (dst_c
->file
.length
>= (off_t
)dest
->upload_temp_file_size
) {
553 /* the chunk is too large now, close it */
554 int rc
= close(dst_c
->file
.fd
);
557 log_error_write(srv
, __FILE__
, __LINE__
, "sbss",
558 "close() temp-file", dst_c
->file
.name
, "failed:",
568 if (NULL
== dst_c
&& NULL
== (dst_c
= chunkqueue_get_append_tempfile(srv
, dest
))) {
572 if (dst_c
->file
.fd
< 0) return -1;
575 /* (dst_c->file.fd >= 0) */
576 /* coverity[negative_returns : FALSE] */
577 written
= write(dst_c
->file
.fd
, mem
, len
);
579 if ((size_t) written
== len
) {
580 dst_c
->file
.length
+= len
;
581 dest
->bytes_in
+= len
;
584 } else if (written
>= 0) {
585 /*(assume EINTR if partial write and retry write();
586 * retry write() might fail with ENOSPC if no more space on volume)*/
587 dest
->bytes_in
+= written
;
589 len
-= (size_t)written
;
590 dst_c
->file
.length
+= (size_t)written
;
591 /* continue; retry */
592 } else if (errno
== EINTR
) {
593 /* continue; retry */
595 int retry
= (errno
== ENOSPC
&& dest
->tempdirs
&& ++dest
->tempdir_idx
< dest
->tempdirs
->used
);
597 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
598 "write() temp-file", dst_c
->file
.name
, "failed:",
602 if (0 == chunk_remaining_length(dst_c
)) {
603 /*(remove empty chunk and unlink tempfile)*/
604 chunkqueue_remove_empty_chunks(dest
);
605 } else {/*(close tempfile; avoid later attempts to append)*/
606 int rc
= close(dst_c
->file
.fd
);
609 log_error_write(srv
, __FILE__
, __LINE__
, "sbss",
610 "close() temp-file", dst_c
->file
.name
, "failed:",
615 if (!retry
) break; /* return -1; */
617 /* continue; retry */
625 int chunkqueue_steal_with_tempfiles(server
*srv
, chunkqueue
*dest
, chunkqueue
*src
, off_t len
) {
627 chunk
*c
= src
->first
;
630 if (NULL
== c
) break;
632 clen
= chunk_remaining_length(c
);
634 /* drop empty chunk */
635 src
->first
= c
->next
;
636 if (c
== src
->last
) src
->last
= NULL
;
637 chunkqueue_push_unused_chunk(src
, c
);
641 use
= (len
>= clen
) ? clen
: len
;
647 /* move complete chunk */
648 src
->first
= c
->next
;
649 if (c
== src
->last
) src
->last
= NULL
;
650 chunkqueue_append_chunk(dest
, c
);
652 /* partial chunk with length "use" */
653 /* tempfile flag is in "last" chunk after the split */
654 chunkqueue_append_file(dest
, c
->file
.name
, c
->file
.start
+ c
->offset
, use
);
657 force_assert(0 == len
);
662 /* store "use" bytes from memory chunk in tempfile */
663 if (0 != chunkqueue_append_mem_to_tempfile(srv
, dest
, c
->mem
->ptr
+ c
->offset
, use
)) {
669 src
->first
= c
->next
;
670 if (c
== src
->last
) src
->last
= NULL
;
671 chunkqueue_push_unused_chunk(src
, c
);
675 force_assert(0 == len
);
680 src
->bytes_out
+= use
;
686 off_t
chunkqueue_length(chunkqueue
*cq
) {
690 for (c
= cq
->first
; c
; c
= c
->next
) {
691 len
+= chunk_remaining_length(c
);
697 void chunkqueue_mark_written(chunkqueue
*cq
, off_t len
) {
700 force_assert(len
>= 0);
702 for (c
= cq
->first
; NULL
!= c
; c
= cq
->first
) {
703 off_t c_len
= chunk_remaining_length(c
);
705 if (0 == written
&& 0 != c_len
) break; /* no more finished chunks */
707 if (written
>= c_len
) { /* chunk got finished */
712 if (c
== cq
->last
) cq
->last
= NULL
;
714 chunkqueue_push_unused_chunk(cq
, c
);
715 } else { /* partial chunk */
716 c
->offset
+= written
;
718 break; /* chunk not finished */
722 force_assert(0 == written
);
723 cq
->bytes_out
+= len
;
726 void chunkqueue_remove_finished_chunks(chunkqueue
*cq
) {
729 for (c
= cq
->first
; c
; c
= cq
->first
) {
730 if (0 != chunk_remaining_length(c
)) break; /* not finished yet */
733 if (c
== cq
->last
) cq
->last
= NULL
;
735 chunkqueue_push_unused_chunk(cq
, c
);
739 static void chunkqueue_remove_empty_chunks(chunkqueue
*cq
) {
741 chunkqueue_remove_finished_chunks(cq
);
742 if (chunkqueue_is_empty(cq
)) return;
744 for (c
= cq
->first
; c
->next
; c
= c
->next
) {
745 if (0 == chunk_remaining_length(c
->next
)) {
746 chunk
*empty
= c
->next
;
747 c
->next
= empty
->next
;
748 if (empty
== cq
->last
) cq
->last
= c
;
750 chunkqueue_push_unused_chunk(cq
, empty
);
755 int chunkqueue_open_file_chunk(server
*srv
, chunkqueue
*cq
) {
756 chunk
* const c
= cq
->first
;
757 off_t offset
, toSend
;
760 force_assert(NULL
!= c
);
761 force_assert(FILE_CHUNK
== c
->type
);
762 force_assert(c
->offset
>= 0 && c
->offset
<= c
->file
.length
);
764 offset
= c
->file
.start
+ c
->offset
;
765 toSend
= c
->file
.length
- c
->offset
;
767 if (-1 == c
->file
.fd
) {
768 if (-1 == (c
->file
.fd
= fdevent_open_cloexec(c
->file
.name
->ptr
, O_RDONLY
, 0))) {
769 log_error_write(srv
, __FILE__
, __LINE__
, "ssb", "open failed:", strerror(errno
), c
->file
.name
);
774 /*(skip file size checks if file is temp file created by lighttpd)*/
775 if (c
->file
.is_temp
) return 0;
777 if (-1 == fstat(c
->file
.fd
, &st
)) {
778 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "fstat failed:", strerror(errno
));
782 if (offset
> st
.st_size
|| toSend
> st
.st_size
|| offset
> st
.st_size
- toSend
) {
783 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "file shrunk:", c
->file
.name
);