4 * the network chunk-API
13 #include <sys/types.h>
25 chunkqueue
*chunkqueue_init(void) {
28 cq
= calloc(1, sizeof(*cq
));
29 force_assert(NULL
!= cq
);
39 static chunk
*chunk_init(void) {
42 c
= calloc(1, sizeof(*c
));
43 force_assert(NULL
!= c
);
46 c
->mem
= buffer_init();
47 c
->file
.name
= buffer_init();
48 c
->file
.start
= c
->file
.length
= c
->file
.mmap
.offset
= 0;
50 c
->file
.mmap
.start
= MAP_FAILED
;
51 c
->file
.mmap
.length
= 0;
59 static void chunk_reset(chunk
*c
) {
60 if (NULL
== c
) return;
66 if (c
->file
.is_temp
&& !buffer_string_is_empty(c
->file
.name
)) {
67 unlink(c
->file
.name
->ptr
);
70 buffer_reset(c
->file
.name
);
72 if (c
->file
.fd
!= -1) {
76 if (MAP_FAILED
!= c
->file
.mmap
.start
) {
77 munmap(c
->file
.mmap
.start
, c
->file
.mmap
.length
);
78 c
->file
.mmap
.start
= MAP_FAILED
;
80 c
->file
.start
= c
->file
.length
= c
->file
.mmap
.offset
= 0;
81 c
->file
.mmap
.length
= 0;
87 static void chunk_free(chunk
*c
) {
88 if (NULL
== c
) return;
93 buffer_free(c
->file
.name
);
98 static off_t
chunk_remaining_length(const chunk
*c
) {
102 len
= buffer_string_length(c
->mem
);
105 len
= c
->file
.length
;
108 force_assert(c
->type
== MEM_CHUNK
|| c
->type
== FILE_CHUNK
);
111 force_assert(c
->offset
<= len
);
112 return len
- c
->offset
;
115 void chunkqueue_free(chunkqueue
*cq
) {
118 if (NULL
== cq
) return;
120 for (c
= cq
->first
; c
; ) {
126 for (c
= cq
->unused
; c
; ) {
135 static void chunkqueue_push_unused_chunk(chunkqueue
*cq
, chunk
*c
) {
136 force_assert(NULL
!= cq
&& NULL
!= c
);
138 /* keep at max 4 chunks in the 'unused'-cache */
139 if (cq
->unused_chunks
> 4) {
143 c
->next
= cq
->unused
;
149 static chunk
*chunkqueue_get_unused_chunk(chunkqueue
*cq
) {
152 force_assert(NULL
!= cq
);
154 /* check if we have a unused chunk */
155 if (0 == cq
->unused
) {
158 /* take the first element from the list (a stack) */
160 cq
->unused
= c
->next
;
168 static void chunkqueue_prepend_chunk(chunkqueue
*cq
, chunk
*c
) {
172 if (NULL
== cq
->last
) {
175 cq
->bytes_in
+= chunk_remaining_length(c
);
178 static void chunkqueue_append_chunk(chunkqueue
*cq
, chunk
*c
) {
185 if (NULL
== cq
->first
) {
188 cq
->bytes_in
+= chunk_remaining_length(c
);
191 void chunkqueue_reset(chunkqueue
*cq
) {
192 chunk
*cur
= cq
->first
;
194 cq
->first
= cq
->last
= NULL
;
196 while (NULL
!= cur
) {
197 chunk
*next
= cur
->next
;
198 chunkqueue_push_unused_chunk(cq
, cur
);
207 void chunkqueue_append_file_fd(chunkqueue
*cq
, buffer
*fn
, int fd
, off_t offset
, off_t len
) {
215 c
= chunkqueue_get_unused_chunk(cq
);
217 c
->type
= FILE_CHUNK
;
219 buffer_copy_buffer(c
->file
.name
, fn
);
220 c
->file
.start
= offset
;
221 c
->file
.length
= len
;
225 chunkqueue_append_chunk(cq
, c
);
228 void chunkqueue_append_file(chunkqueue
*cq
, buffer
*fn
, off_t offset
, off_t len
) {
231 if (0 == len
) return;
233 c
= chunkqueue_get_unused_chunk(cq
);
235 c
->type
= FILE_CHUNK
;
237 buffer_copy_buffer(c
->file
.name
, fn
);
238 c
->file
.start
= offset
;
239 c
->file
.length
= len
;
242 chunkqueue_append_chunk(cq
, c
);
245 void chunkqueue_append_buffer(chunkqueue
*cq
, buffer
*mem
) {
248 if (buffer_string_is_empty(mem
)) return;
250 c
= chunkqueue_get_unused_chunk(cq
);
252 force_assert(NULL
!= c
->mem
);
253 buffer_move(c
->mem
, mem
);
255 chunkqueue_append_chunk(cq
, c
);
258 void chunkqueue_prepend_buffer(chunkqueue
*cq
, buffer
*mem
) {
261 if (buffer_string_is_empty(mem
)) return;
263 c
= chunkqueue_get_unused_chunk(cq
);
265 force_assert(NULL
!= c
->mem
);
266 buffer_move(c
->mem
, mem
);
268 chunkqueue_prepend_chunk(cq
, c
);
272 void chunkqueue_append_mem(chunkqueue
*cq
, const char * mem
, size_t len
) {
275 if (0 == len
) return;
277 c
= chunkqueue_get_unused_chunk(cq
);
279 buffer_copy_string_len(c
->mem
, mem
, len
);
281 chunkqueue_append_chunk(cq
, c
);
284 void chunkqueue_get_memory(chunkqueue
*cq
, char **mem
, size_t *len
, size_t min_size
, size_t alloc_size
) {
285 static const size_t REALLOC_MAX_SIZE
= 256;
291 force_assert(NULL
!= cq
);
292 if (NULL
== mem
) mem
= &dummy_mem
;
293 if (NULL
== len
) len
= &dummy_len
;
295 /* default values: */
296 if (0 == min_size
) min_size
= 1024;
297 if (0 == alloc_size
) alloc_size
= 4096;
298 if (alloc_size
< min_size
) alloc_size
= min_size
;
300 if (NULL
!= cq
->last
&& MEM_CHUNK
== cq
->last
->type
) {
304 have
= buffer_string_space(b
);
306 /* unused buffer: allocate space */
307 if (buffer_string_is_empty(b
)) {
308 buffer_string_prepare_copy(b
, alloc_size
);
309 have
= buffer_string_space(b
);
311 /* if buffer is really small just make it bigger */
312 else if (have
< min_size
&& b
->size
<= REALLOC_MAX_SIZE
) {
313 size_t cur_len
= buffer_string_length(b
);
314 size_t new_size
= cur_len
+ min_size
, append
;
315 if (new_size
< alloc_size
) new_size
= alloc_size
;
317 append
= new_size
- cur_len
;
318 if (append
>= min_size
) {
319 buffer_string_prepare_append(b
, append
);
320 have
= buffer_string_space(b
);
324 /* return pointer into existing buffer if large enough */
325 if (have
>= min_size
) {
326 *mem
= b
->ptr
+ buffer_string_length(b
);
332 /* allocate new chunk */
333 c
= chunkqueue_get_unused_chunk(cq
);
335 chunkqueue_append_chunk(cq
, c
);
338 buffer_string_prepare_append(b
, alloc_size
);
340 *mem
= b
->ptr
+ buffer_string_length(b
);
341 *len
= buffer_string_space(b
);
344 void chunkqueue_use_memory(chunkqueue
*cq
, size_t len
) {
347 force_assert(NULL
!= cq
);
348 force_assert(NULL
!= cq
->last
&& MEM_CHUNK
== cq
->last
->type
);
352 buffer_commit(b
, len
);
354 } else if (buffer_string_is_empty(b
)) {
355 /* unused buffer: can't remove chunk easily from
356 * end of list, so just reset the buffer
362 /* default 1MB, upper limit 128MB */
363 #define DEFAULT_TEMPFILE_SIZE (1 * 1024 * 1024)
364 #define MAX_TEMPFILE_SIZE (128 * 1024 * 1024)
366 void chunkqueue_set_tempdirs(chunkqueue
*cq
, array
*tempdirs
, unsigned int upload_temp_file_size
) {
367 force_assert(NULL
!= cq
);
368 cq
->tempdirs
= tempdirs
;
369 cq
->upload_temp_file_size
370 = (0 == upload_temp_file_size
) ? DEFAULT_TEMPFILE_SIZE
371 : (upload_temp_file_size
> MAX_TEMPFILE_SIZE
) ? MAX_TEMPFILE_SIZE
372 : upload_temp_file_size
;
376 void chunkqueue_steal(chunkqueue
*dest
, chunkqueue
*src
, off_t len
) {
378 chunk
*c
= src
->first
;
381 if (NULL
== c
) break;
383 clen
= chunk_remaining_length(c
);
385 /* drop empty chunk */
386 src
->first
= c
->next
;
387 if (c
== src
->last
) src
->last
= NULL
;
388 chunkqueue_push_unused_chunk(src
, c
);
392 use
= len
>= clen
? clen
: len
;
396 /* move complete chunk */
397 src
->first
= c
->next
;
398 if (c
== src
->last
) src
->last
= NULL
;
400 chunkqueue_append_chunk(dest
, c
);
402 /* partial chunk with length "use" */
406 chunkqueue_append_mem(dest
, c
->mem
->ptr
+ c
->offset
, use
);
409 /* tempfile flag is in "last" chunk after the split */
410 chunkqueue_append_file(dest
, c
->file
.name
, c
->file
.start
+ c
->offset
, use
);
415 force_assert(0 == len
);
418 src
->bytes_out
+= use
;
422 static chunk
*chunkqueue_get_append_tempfile(chunkqueue
*cq
) {
424 buffer
*template = buffer_init_string("/var/tmp/lighttpd-upload-XXXXXX");
427 if (cq
->tempdirs
&& cq
->tempdirs
->used
) {
428 /* we have several tempdirs, only if all of them fail we jump out */
430 for (errno
= EIO
; cq
->tempdir_idx
< cq
->tempdirs
->used
; ++cq
->tempdir_idx
) {
431 data_string
*ds
= (data_string
*)cq
->tempdirs
->data
[cq
->tempdir_idx
];
433 buffer_copy_buffer(template, ds
->value
);
434 buffer_append_slash(template);
435 buffer_append_string_len(template, CONST_STR_LEN("lighttpd-upload-XXXXXX"));
437 if (-1 != (fd
= mkstemp(template->ptr
))) break;
440 fd
= mkstemp(template->ptr
);
444 buffer_free(template);
448 c
= chunkqueue_get_unused_chunk(cq
);
449 c
->type
= FILE_CHUNK
;
452 buffer_copy_buffer(c
->file
.name
, template);
455 chunkqueue_append_chunk(cq
, c
);
457 buffer_free(template);
462 static void chunkqueue_remove_empty_chunks(chunkqueue
*cq
);
464 static int chunkqueue_append_to_tempfile(server
*srv
, chunkqueue
*dest
, const char *mem
, size_t len
) {
470 * if the last chunk is
471 * - smaller than dest->upload_temp_file_size
472 * - not read yet (offset == 0)
473 * -> append to it (so it might actually become larger than dest->upload_temp_file_size)
475 * -> create a new chunk
481 && FILE_CHUNK
== dst_c
->type
482 && dst_c
->file
.is_temp
483 && -1 != dst_c
->file
.fd
484 && 0 == dst_c
->offset
) {
485 /* ok, take the last chunk for our job */
487 if (dst_c
->file
.length
>= (off_t
)dest
->upload_temp_file_size
) {
488 /* the chunk is too large now, close it */
489 int rc
= close(dst_c
->file
.fd
);
493 log_error_write(srv
, __FILE__
, __LINE__
, "sbss",
494 "close() temp-file", dst_c
->file
.name
, "failed:",
503 if (NULL
== dst_c
&& NULL
== (dst_c
= chunkqueue_get_append_tempfile(dest
))) {
504 /* we don't have file to write to,
505 * EACCES might be one reason.
507 * Instead of sending 500 we send 413 and say the request is too large
510 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
511 "opening temp-file failed:", strerror(errno
));
516 written
= write(dst_c
->file
.fd
, mem
, len
);
518 if ((size_t) written
== len
) {
519 dst_c
->file
.length
+= len
;
520 dest
->bytes_in
+= len
;
523 } else if (written
>= 0) {
524 /*(assume EINTR if partial write and retry write();
525 * retry write() might fail with ENOSPC if no more space on volume)*/
526 dest
->bytes_in
+= written
;
528 len
-= (size_t)written
;
529 dst_c
->file
.length
+= (size_t)written
;
530 /* continue; retry */
531 } else if (errno
== EINTR
) {
532 /* continue; retry */
534 int retry
= (errno
== ENOSPC
&& dest
->tempdirs
&& ++dest
->tempdir_idx
< dest
->tempdirs
->used
);
536 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
537 "write() temp-file", dst_c
->file
.name
, "failed:",
541 if (0 == chunk_remaining_length(dst_c
)) {
542 /*(remove empty chunk and unlink tempfile)*/
543 chunkqueue_remove_empty_chunks(dest
);
544 } else {/*(close tempfile; avoid later attempts to append)*/
545 int rc
= close(dst_c
->file
.fd
);
548 log_error_write(srv
, __FILE__
, __LINE__
, "sbss",
549 "close() temp-file", dst_c
->file
.name
, "failed:",
554 if (!retry
) return -1;
556 /* continue; retry */
561 return -1; /*(not reached)*/
564 int chunkqueue_steal_with_tempfiles(server
*srv
, chunkqueue
*dest
, chunkqueue
*src
, off_t len
) {
566 chunk
*c
= src
->first
;
569 if (NULL
== c
) break;
571 clen
= chunk_remaining_length(c
);
573 /* drop empty chunk */
574 src
->first
= c
->next
;
575 if (c
== src
->last
) src
->last
= NULL
;
576 chunkqueue_push_unused_chunk(src
, c
);
580 use
= (len
>= clen
) ? clen
: len
;
586 /* move complete chunk */
587 src
->first
= c
->next
;
588 if (c
== src
->last
) src
->last
= NULL
;
589 chunkqueue_append_chunk(dest
, c
);
591 /* partial chunk with length "use" */
592 /* tempfile flag is in "last" chunk after the split */
593 chunkqueue_append_file(dest
, c
->file
.name
, c
->file
.start
+ c
->offset
, use
);
596 force_assert(0 == len
);
601 /* store "use" bytes from memory chunk in tempfile */
602 if (0 != chunkqueue_append_to_tempfile(srv
, dest
, c
->mem
->ptr
+ c
->offset
, use
)) {
608 src
->first
= c
->next
;
609 if (c
== src
->last
) src
->last
= NULL
;
610 chunkqueue_push_unused_chunk(src
, c
);
614 force_assert(0 == len
);
619 src
->bytes_out
+= use
;
625 off_t
chunkqueue_length(chunkqueue
*cq
) {
629 for (c
= cq
->first
; c
; c
= c
->next
) {
630 len
+= chunk_remaining_length(c
);
636 int chunkqueue_is_empty(chunkqueue
*cq
) {
637 return NULL
== cq
->first
;
640 void chunkqueue_mark_written(chunkqueue
*cq
, off_t len
) {
643 force_assert(len
>= 0);
645 for (c
= cq
->first
; NULL
!= c
; c
= cq
->first
) {
646 off_t c_len
= chunk_remaining_length(c
);
648 if (0 == written
&& 0 != c_len
) break; /* no more finished chunks */
650 if (written
>= c_len
) { /* chunk got finished */
655 if (c
== cq
->last
) cq
->last
= NULL
;
657 chunkqueue_push_unused_chunk(cq
, c
);
658 } else { /* partial chunk */
659 c
->offset
+= written
;
661 break; /* chunk not finished */
665 force_assert(0 == written
);
666 cq
->bytes_out
+= len
;
669 void chunkqueue_remove_finished_chunks(chunkqueue
*cq
) {
672 for (c
= cq
->first
; c
; c
= cq
->first
) {
673 if (0 != chunk_remaining_length(c
)) break; /* not finished yet */
676 if (c
== cq
->last
) cq
->last
= NULL
;
678 chunkqueue_push_unused_chunk(cq
, c
);
682 static void chunkqueue_remove_empty_chunks(chunkqueue
*cq
) {
684 chunkqueue_remove_finished_chunks(cq
);
685 if (chunkqueue_is_empty(cq
)) return;
687 for (c
= cq
->first
; c
->next
; c
= c
->next
) {
688 if (0 == chunk_remaining_length(c
->next
)) {
689 chunk
*empty
= c
->next
;
690 c
->next
= empty
->next
;
691 if (empty
== cq
->last
) cq
->last
= c
;
693 chunkqueue_push_unused_chunk(cq
, empty
);