[core] consolidate backend network write handlers
[lighttpd.git] / src / network_write.c
blob240e9b5991e420fdb4cf7c7a4954533d3810b63e
1 #include "first.h"
3 #include "network_write.h"
5 #include "base.h"
6 #include "log.h"
8 #include <sys/types.h>
9 #include "sys-socket.h"
11 #include <errno.h>
12 #include <string.h>
13 #include <unistd.h>
16 /* on linux 2.4.x you get either sendfile or LFS */
17 #if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE \
18 && (!defined _LARGEFILE_SOURCE || defined HAVE_SENDFILE64) \
19 && defined(__linux__) && !defined HAVE_SENDFILE_BROKEN
20 # ifdef NETWORK_WRITE_USE_SENDFILE
21 # error "can't have more than one sendfile implementation"
22 # endif
23 # define NETWORK_WRITE_USE_SENDFILE "linux-sendfile"
24 # define NETWORK_WRITE_USE_LINUX_SENDFILE
25 #endif
27 #if defined HAVE_SENDFILE && (defined(__FreeBSD__) || defined(__DragonFly__))
28 # ifdef NETWORK_WRITE_USE_SENDFILE
29 # error "can't have more than one sendfile implementation"
30 # endif
31 # define NETWORK_WRITE_USE_SENDFILE "freebsd-sendfile"
32 # define NETWORK_WRITE_USE_FREEBSD_SENDFILE
33 #endif
35 #if defined HAVE_SENDFILE && defined(__APPLE__)
36 # ifdef NETWORK_WRITE_USE_SENDFILE
37 # error "can't have more than one sendfile implementation"
38 # endif
39 # define NETWORK_WRITE_USE_SENDFILE "darwin-sendfile"
40 # define NETWORK_WRITE_USE_DARWIN_SENDFILE
41 #endif
43 #if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILEV && defined(__sun)
44 # ifdef NETWORK_WRITE_USE_SENDFILE
45 # error "can't have more than one sendfile implementation"
46 # endif
47 # define NETWORK_WRITE_USE_SENDFILE "solaris-sendfilev"
48 # define NETWORK_WRITE_USE_SOLARIS_SENDFILEV
49 #endif
51 /* not supported so far
52 #if defined HAVE_SEND_FILE && defined(__aix)
53 # ifdef NETWORK_WRITE_USE_SENDFILE
54 # error "can't have more than one sendfile implementation"
55 # endif
56 # define NETWORK_WRITE_USE_SENDFILE "aix-sendfile"
57 # define NETWORK_WRITE_USE_AIX_SENDFILE
58 #endif
61 #if defined HAVE_SYS_UIO_H && defined HAVE_WRITEV
62 # define NETWORK_WRITE_USE_WRITEV
63 #endif
65 #if defined HAVE_SYS_MMAN_H && defined HAVE_MMAP && defined ENABLE_MMAP
66 # define NETWORK_WRITE_USE_MMAP
67 #endif
70 static int network_write_error(server *srv, int fd) {
71 #if defined(__WIN32)
72 int lastError = WSAGetLastError();
73 switch (lastError) {
74 case WSAEINTR:
75 case WSAEWOULDBLOCK:
76 return -3;
77 case WSAECONNRESET:
78 case WSAETIMEDOUT:
79 case WSAECONNABORTED:
80 return -2;
81 default:
82 log_error_write(srv, __FILE__, __LINE__, "sdd",
83 "send failed: ", lastError, fd);
84 return -1;
86 #else /* __WIN32 */
87 switch (errno) {
88 case EAGAIN:
89 case EINTR:
90 return -3;
91 case EPIPE:
92 case ECONNRESET:
93 return -2;
94 default:
95 log_error_write(srv, __FILE__, __LINE__, "ssd",
96 "write failed:", strerror(errno), fd);
97 return -1;
99 #endif /* __WIN32 */
102 inline
103 static ssize_t network_write_data_len(int fd, const char *data, off_t len) {
104 #if defined(__WIN32)
105 return send(fd, data, len, 0);
106 #else /* __WIN32 */
107 return write(fd, data, len);
108 #endif /* __WIN32 */
114 /* write next chunk(s); finished chunks are removed afterwards after successful writes.
115 * return values: similar as backends (0 succes, -1 error, -2 remote close, -3 try again later (EINTR/EAGAIN)) */
116 /* next chunk must be MEM_CHUNK. use write()/send() */
117 static int network_write_mem_chunk(server *srv, int fd, chunkqueue *cq, off_t *p_max_bytes) {
118 chunk* const c = cq->first;
119 ssize_t wr;
120 off_t c_len = (off_t)buffer_string_length(c->mem);
121 force_assert(c->offset >= 0 && c->offset <= c_len);
122 c_len -= c->offset;
123 if (c_len > *p_max_bytes) c_len = *p_max_bytes;
125 if (0 == c_len) {
126 chunkqueue_remove_finished_chunks(cq);
127 return 0;
130 wr = network_write_data_len(fd, c->mem->ptr + c->offset, c_len);
131 if (wr >= 0) {
132 *p_max_bytes -= wr;
133 chunkqueue_mark_written(cq, wr);
134 return (wr > 0 && wr == c_len) ? 0 : -3;
135 } else {
136 return network_write_error(srv, fd);
143 #if !defined(NETWORK_WRITE_USE_MMAP)
145 static int network_write_file_chunk_no_mmap(server *srv, int fd, chunkqueue *cq, off_t *p_max_bytes) {
146 chunk* const c = cq->first;
147 off_t offset, toSend;
148 ssize_t wr;
150 force_assert(c->offset >= 0 && c->offset <= c->file.length);
152 offset = c->file.start + c->offset;
153 toSend = c->file.length - c->offset;
154 if (toSend > *p_max_bytes) toSend = *p_max_bytes;
156 if (0 == toSend) {
157 chunkqueue_remove_finished_chunks(cq);
158 return 0;
161 if (0 != chunkqueue_open_file_chunk(srv, cq)) return -1;
163 if (toSend > 64*1024) toSend = 64*1024; /* max read 64kb in one step */
164 buffer_string_prepare_copy(srv->tmp_buf, toSend);
166 if (-1 == lseek(c->file.fd, offset, SEEK_SET)) {
167 log_error_write(srv, __FILE__, __LINE__, "ss","lseek:",strerror(errno));
168 return -1;
170 if (-1 == (toSend = read(c->file.fd, srv->tmp_buf->ptr, toSend))) {
171 log_error_write(srv, __FILE__, __LINE__, "ss","read:",strerror(errno));
172 return -1;
175 wr = network_write_data_len(fd, srv->tmp_buf->ptr, toSend);
176 if (wr >= 0) {
177 *p_max_bytes -= wr;
178 chunkqueue_mark_written(cq, wr);
179 return (wr > 0 && wr == toSend) ? 0 : -3;
180 } else {
181 return network_write_error(srv, fd);
185 #endif
190 #if defined(NETWORK_WRITE_USE_MMAP)
192 #include "sys-mmap.h"
194 #include <setjmp.h>
195 #include <signal.h>
197 #define MMAP_CHUNK_SIZE (512*1024)
199 static off_t mmap_align_offset(off_t start) {
200 static long pagesize = 0;
201 if (0 == pagesize) {
202 pagesize = sysconf(_SC_PAGESIZE);
203 force_assert(pagesize < MMAP_CHUNK_SIZE);
205 force_assert(start >= (start % pagesize));
206 return start - (start % pagesize);
209 static volatile int sigbus_jmp_valid;
210 static sigjmp_buf sigbus_jmp;
212 static void sigbus_handler(int sig) {
213 UNUSED(sig);
214 if (sigbus_jmp_valid) siglongjmp(sigbus_jmp, 1);
215 log_failed_assert(__FILE__, __LINE__, "SIGBUS");
218 /* next chunk must be FILE_CHUNK. send mmap()ed file with write() */
219 static int network_write_file_chunk_mmap(server *srv, int fd, chunkqueue *cq, off_t *p_max_bytes) {
220 chunk* const c = cq->first;
221 off_t offset, toSend, file_end;
222 ssize_t r;
223 size_t mmap_offset, mmap_avail;
224 const char *data;
226 force_assert(c->offset >= 0 && c->offset <= c->file.length);
228 offset = c->file.start + c->offset;
229 toSend = c->file.length - c->offset;
230 if (toSend > *p_max_bytes) toSend = *p_max_bytes;
231 file_end = c->file.start + c->file.length; /*file end offset in this chunk*/
233 if (0 == toSend) {
234 chunkqueue_remove_finished_chunks(cq);
235 return 0;
238 if (0 != chunkqueue_open_file_chunk(srv, cq)) return -1;
240 /* mmap buffer if offset is outside old mmap area or not mapped at all */
241 if (MAP_FAILED == c->file.mmap.start
242 || offset < c->file.mmap.offset
243 || offset >= (off_t)(c->file.mmap.offset + c->file.mmap.length)) {
245 if (MAP_FAILED != c->file.mmap.start) {
246 munmap(c->file.mmap.start, c->file.mmap.length);
247 c->file.mmap.start = MAP_FAILED;
250 /* Optimizations for the future:
252 * adaptive mem-mapping
253 * the problem:
254 * we mmap() the whole file. If someone has alot large files and
255 * 32-bit machine the virtual address area will be unrun and we
256 * will have a failing mmap() call.
257 * solution:
258 * only mmap 16M in one chunk and move the window as soon as we have
259 * finished the first 8M
261 * read-ahead buffering
262 * the problem:
263 * sending out several large files in parallel trashes read-ahead
264 * of the kernel leading to long wait-for-seek times.
265 * solutions: (increasing complexity)
266 * 1. use madvise
267 * 2. use a internal read-ahead buffer in the chunk-structure
268 * 3. use non-blocking IO for file-transfers
269 * */
271 c->file.mmap.offset = mmap_align_offset(offset);
273 /* all mmap()ed areas are MMAP_CHUNK_SIZE
274 * except the last which might be smaller */
275 c->file.mmap.length = MMAP_CHUNK_SIZE;
276 if (c->file.mmap.offset > file_end - (off_t)c->file.mmap.length) {
277 c->file.mmap.length = file_end - c->file.mmap.offset;
280 c->file.mmap.start = mmap(NULL, c->file.mmap.length, PROT_READ,
281 MAP_SHARED, c->file.fd, c->file.mmap.offset);
282 if (MAP_FAILED == c->file.mmap.start) {
283 log_error_write(srv, __FILE__, __LINE__, "ssbdoo", "mmap failed:",
284 strerror(errno), c->file.name, c->file.fd, c->file.mmap.offset, (off_t) c->file.mmap.length);
285 return -1;
288 #if defined(HAVE_MADVISE)
289 /* don't advise files < 64Kb */
290 if (c->file.mmap.length > (64*1024)) {
291 /* darwin 7 is returning EINVAL all the time and I don't know how to
292 * detect this at runtime.
294 * ignore the return value for now */
295 madvise(c->file.mmap.start, c->file.mmap.length, MADV_WILLNEED);
297 #endif
300 force_assert(offset >= c->file.mmap.offset);
301 mmap_offset = offset - c->file.mmap.offset;
302 force_assert(c->file.mmap.length > mmap_offset);
303 mmap_avail = c->file.mmap.length - mmap_offset;
304 if (toSend > (off_t) mmap_avail) toSend = mmap_avail;
306 data = c->file.mmap.start + mmap_offset;
308 /* setup SIGBUS handler, but don't activate sigbus_jmp_valid yet */
309 if (0 == sigsetjmp(sigbus_jmp, 1)) {
310 signal(SIGBUS, sigbus_handler);
312 sigbus_jmp_valid = 1;
313 r = network_write_data_len(fd, data, toSend);
314 sigbus_jmp_valid = 0;
315 } else {
316 sigbus_jmp_valid = 0;
318 log_error_write(srv, __FILE__, __LINE__, "sbd", "SIGBUS in mmap:",
319 c->file.name, c->file.fd);
321 munmap(c->file.mmap.start, c->file.mmap.length);
322 c->file.mmap.start = MAP_FAILED;
323 return -1;
326 if (r >= 0) {
327 *p_max_bytes -= r;
328 chunkqueue_mark_written(cq, r);
329 return (r > 0 && r == toSend) ? 0 : -3;
330 } else {
331 return network_write_error(srv, fd);
335 #endif /* NETWORK_WRITE_USE_MMAP */
340 #if defined(NETWORK_WRITE_USE_WRITEV)
342 #if defined(HAVE_SYS_UIO_H)
343 # include <sys/uio.h>
344 #endif
346 #if defined(UIO_MAXIOV)
347 # define SYS_MAX_CHUNKS UIO_MAXIOV
348 #elif defined(IOV_MAX)
349 /* new name for UIO_MAXIOV since IEEE Std 1003.1-2001 */
350 # define SYS_MAX_CHUNKS IOV_MAX
351 #elif defined(_XOPEN_IOV_MAX)
352 /* minimum value for sysconf(_SC_IOV_MAX); posix requires this to be at least 16, which is good enough - no need to call sysconf() */
353 # define SYS_MAX_CHUNKS _XOPEN_IOV_MAX
354 #else
355 # error neither UIO_MAXIOV nor IOV_MAX nor _XOPEN_IOV_MAX are defined
356 #endif
358 /* allocate iovec[MAX_CHUNKS] on stack, so pick a sane limit:
359 * - each entry will use 1 pointer + 1 size_t
360 * - 32 chunks -> 256 / 512 bytes (32-bit/64-bit pointers)
362 #define STACK_MAX_ALLOC_CHUNKS 32
363 #if SYS_MAX_CHUNKS > STACK_MAX_ALLOC_CHUNKS
364 # define MAX_CHUNKS STACK_MAX_ALLOC_CHUNKS
365 #else
366 # define MAX_CHUNKS SYS_MAX_CHUNKS
367 #endif
369 /* next chunk must be MEM_CHUNK. send multiple mem chunks using writev() */
370 static int network_writev_mem_chunks(server *srv, int fd, chunkqueue *cq, off_t *p_max_bytes) {
371 struct iovec chunks[MAX_CHUNKS];
372 size_t num_chunks = 0;
373 off_t max_bytes = *p_max_bytes;
374 off_t toSend = 0;
375 ssize_t r;
377 for (const chunk *c = cq->first;
378 NULL != c && MEM_CHUNK == c->type
379 && num_chunks < MAX_CHUNKS && toSend < max_bytes;
380 c = c->next) {
381 size_t c_len = buffer_string_length(c->mem);
382 force_assert(c->offset >= 0 && c->offset <= (off_t)c_len);
383 c_len -= c->offset;
384 if (c_len > 0) {
385 toSend += c_len;
387 chunks[num_chunks].iov_base = c->mem->ptr + c->offset;
388 chunks[num_chunks].iov_len = c_len;
390 ++num_chunks;
394 if (0 == num_chunks) {
395 chunkqueue_remove_finished_chunks(cq);
396 return 0;
399 r = writev(fd, chunks, num_chunks);
401 if (r < 0) switch (errno) {
402 case EAGAIN:
403 case EINTR:
404 break;
405 case EPIPE:
406 case ECONNRESET:
407 return -2;
408 default:
409 log_error_write(srv, __FILE__, __LINE__, "ssd",
410 "writev failed:", strerror(errno), fd);
411 return -1;
414 if (r >= 0) {
415 *p_max_bytes -= r;
416 chunkqueue_mark_written(cq, r);
419 return (r > 0 && r == toSend) ? 0 : -3;
422 #endif /* NETWORK_WRITE_USE_WRITEV */
427 #if defined(NETWORK_WRITE_USE_SENDFILE)
429 #if defined(NETWORK_WRITE_USE_LINUX_SENDFILE) \
430 || defined(NETWORK_WRITE_USE_SOLARIS_SENDFILEV)
431 #include <sys/sendfile.h>
432 #endif
434 #if defined(NETWORK_WRITE_USE_FREEBSD_SENDFILE) \
435 || defined(NETWORK_WRITE_USE_DARWIN_SENDFILE)
436 #include <sys/uio.h>
437 #endif
439 static int network_write_file_chunk_sendfile(server *srv, int fd, chunkqueue *cq, off_t *p_max_bytes) {
440 chunk * const c = cq->first;
441 ssize_t r;
442 off_t offset;
443 off_t toSend;
444 off_t written = 0;
446 force_assert(c->offset >= 0 && c->offset <= c->file.length);
448 offset = c->file.start + c->offset;
449 toSend = c->file.length - c->offset;
450 if (toSend > *p_max_bytes) toSend = *p_max_bytes;
452 if (0 == toSend) {
453 chunkqueue_remove_finished_chunks(cq);
454 return 0;
457 if (0 != chunkqueue_open_file_chunk(srv, cq)) return -1;
459 /* Darwin, FreeBSD, and Solaris variants support iovecs and could
460 * be optimized to send more than just file in single syscall */
462 #if defined(NETWORK_WRITE_USE_LINUX_SENDFILE)
464 r = sendfile(fd, c->file.fd, &offset, toSend);
465 if (r > 0) written = (off_t)r;
467 #elif defined(NETWORK_WRITE_USE_DARWIN_SENDFILE)
469 written = toSend;
470 r = sendfile(c->file.fd, fd, offset, &written, NULL, 0);
471 /* (for EAGAIN/EINTR written still contains the sent bytes) */
473 #elif defined(NETWORK_WRITE_USE_FREEBSD_SENDFILE)
475 r = sendfile(c->file.fd, fd, offset, toSend, NULL, &written, 0);
476 /* (for EAGAIN/EINTR written still contains the sent bytes) */
478 #elif defined(NETWORK_WRITE_USE_SOLARIS_SENDFILEV)
480 sendfilevec_t fvec;
481 fvec.sfv_fd = c->file.fd;
482 fvec.sfv_flag = 0;
483 fvec.sfv_off = offset;
484 fvec.sfv_len = toSend;
486 /* Solaris sendfilev() */
487 r = sendfilev(fd, &fvec, 1, (size_t *)&written);
488 /* (for EAGAIN/EINTR written still contains the sent bytes) */
490 #else
492 r = -1;
493 errno = ENOSYS;
495 #endif
497 if (-1 == r) {
498 switch(errno) {
499 case EAGAIN:
500 case EINTR:
501 break; /* try again later */
502 case EPIPE:
503 case ECONNRESET:
504 case ENOTCONN:
505 return -2;
506 case EINVAL:
507 case ENOSYS:
508 #if defined(ENOTSUP) && (!defined(EOPNOTSUPP) || EOPNOTSUPP != ENOTSUP)
509 case ENOTSUP:
510 #endif
511 #ifdef EOPNOTSUPP
512 case EOPNOTSUPP:
513 #endif
514 #ifdef ESOCKTNOSUPPORT
515 case ESOCKTNOSUPPORT:
516 #endif
517 #ifdef EAFNOSUPPORT
518 case EAFNOSUPPORT:
519 #endif
520 #ifdef NETWORK_WRITE_USE_MMAP
521 return network_write_file_chunk_mmap(srv, fd, cq, p_max_bytes);
522 #else
523 return network_write_file_chunk_no_mmap(srv, fd, cq, p_max_bytes);
524 #endif
525 default:
526 log_error_write(srv, __FILE__, __LINE__, "ssdSd",
527 "sendfile():", strerror(errno), errno, "fd:", fd);
528 return -1;
532 if (written >= 0) { /*(always true)*/
533 chunkqueue_mark_written(cq, written);
534 *p_max_bytes -= written;
537 return (r >= 0 && written == toSend) ? 0 : -3;
540 #endif
545 /* return values:
546 * >= 0 : no error
547 * -1 : error (on our side)
548 * -2 : remote close
551 static int network_write_chunkqueue_write(server *srv, int fd, chunkqueue *cq, off_t max_bytes) {
552 while (max_bytes > 0 && NULL != cq->first) {
553 int r = -1;
555 switch (cq->first->type) {
556 case MEM_CHUNK:
557 r = network_write_mem_chunk(srv, fd, cq, &max_bytes);
558 break;
559 case FILE_CHUNK:
560 #ifdef NETWORK_WRITE_USE_MMAP
561 r = network_write_file_chunk_mmap(srv, fd, cq, &max_bytes);
562 #else
563 r = network_write_file_chunk_no_mmap(srv, fd, cq, &max_bytes);
564 #endif
565 break;
568 if (-3 == r) return 0;
569 if (0 != r) return r;
572 return 0;
575 #if defined(NETWORK_WRITE_USE_WRITEV)
576 static int network_write_chunkqueue_writev(server *srv, int fd, chunkqueue *cq, off_t max_bytes) {
577 while (max_bytes > 0 && NULL != cq->first) {
578 int r = -1;
580 switch (cq->first->type) {
581 case MEM_CHUNK:
582 #if defined(NETWORK_WRITE_USE_WRITEV)
583 r = network_writev_mem_chunks(srv, fd, cq, &max_bytes);
584 #else
585 r = network_write_mem_chunk(srv, fd, cq, &max_bytes);
586 #endif
587 break;
588 case FILE_CHUNK:
589 #ifdef NETWORK_WRITE_USE_MMAP
590 r = network_write_file_chunk_mmap(srv, fd, cq, &max_bytes);
591 #else
592 r = network_write_file_chunk_no_mmap(srv, fd, cq, &max_bytes);
593 #endif
594 break;
597 if (-3 == r) return 0;
598 if (0 != r) return r;
601 return 0;
603 #endif
605 #if defined(NETWORK_WRITE_USE_SENDFILE)
606 static int network_write_chunkqueue_sendfile(server *srv, int fd, chunkqueue *cq, off_t max_bytes) {
607 while (max_bytes > 0 && NULL != cq->first) {
608 int r = -1;
610 switch (cq->first->type) {
611 case MEM_CHUNK:
612 #if defined(NETWORK_WRITE_USE_WRITEV)
613 r = network_writev_mem_chunks(srv, fd, cq, &max_bytes);
614 #else
615 r = network_write_mem_chunk(srv, fd, cq, &max_bytes);
616 #endif
617 break;
618 case FILE_CHUNK:
619 #if defined(NETWORK_WRITE_USE_SENDFILE)
620 r = network_write_file_chunk_sendfile(srv, fd, cq, &max_bytes);
621 #elif defined(NETWORK_WRITE_USE_MMAP)
622 r = network_write_file_chunk_mmap(srv, fd, cq, &max_bytes);
623 #else
624 r = network_write_file_chunk_no_mmap(srv, fd, cq, &max_bytes);
625 #endif
626 break;
629 if (-3 == r) return 0;
630 if (0 != r) return r;
633 return 0;
635 #endif
637 int network_write_init(server *srv) {
638 typedef enum {
639 NETWORK_BACKEND_UNSET,
640 NETWORK_BACKEND_WRITE,
641 NETWORK_BACKEND_WRITEV,
642 NETWORK_BACKEND_SENDFILE,
643 } network_backend_t;
645 network_backend_t backend;
647 struct nb_map {
648 network_backend_t nb;
649 const char *name;
650 } network_backends[] = {
651 /* lowest id wins */
652 { NETWORK_BACKEND_SENDFILE, "sendfile" },
653 { NETWORK_BACKEND_SENDFILE, "linux-sendfile" },
654 { NETWORK_BACKEND_SENDFILE, "freebsd-sendfile" },
655 { NETWORK_BACKEND_SENDFILE, "solaris-sendfilev" },
656 { NETWORK_BACKEND_WRITEV, "writev" },
657 { NETWORK_BACKEND_WRITE, "write" },
658 { NETWORK_BACKEND_UNSET, NULL }
661 /* get a useful default */
662 backend = network_backends[0].nb;
664 /* match name against known types */
665 if (!buffer_string_is_empty(srv->srvconf.network_backend)) {
666 const char *name;
667 for (size_t i = 0; NULL != (name = network_backends[i].name); ++i) {
668 if (0 == strcmp(srv->srvconf.network_backend->ptr, name)) {
669 backend = network_backends[i].nb;
670 break;
673 if (NULL == name) {
674 log_error_write(srv, __FILE__, __LINE__, "sb",
675 "server.network-backend has an unknown value:",
676 srv->srvconf.network_backend);
677 return -1;
681 switch(backend) {
682 case NETWORK_BACKEND_SENDFILE:
683 #if defined(NETWORK_WRITE_USE_SENDFILE)
684 srv->network_backend_write = network_write_chunkqueue_sendfile;
685 break;
686 #endif
687 case NETWORK_BACKEND_WRITEV:
688 #if defined(NETWORK_WRITE_USE_WRITEV)
689 srv->network_backend_write = network_write_chunkqueue_writev;
690 break;
691 #endif
692 case NETWORK_BACKEND_WRITE:
693 srv->network_backend_write = network_write_chunkqueue_write;
694 break;
695 default:
696 return -1;
699 return 0;
702 const char * network_write_show_handlers(void) {
703 return
704 "\nNetwork handler:\n\n"
705 #if defined NETWORK_WRITE_USE_LINUX_SENDFILE
706 "\t+ linux-sendfile\n"
707 #else
708 "\t- linux-sendfile\n"
709 #endif
710 #if defined NETWORK_WRITE_USE_FREEBSD_SENDFILE
711 "\t+ freebsd-sendfile\n"
712 #else
713 "\t- freebsd-sendfile\n"
714 #endif
715 #if defined NETWORK_WRITE_USE_DARWIN_SENDFILE
716 "\t+ darwin-sendfile\n"
717 #else
718 "\t- darwin-sendfile\n"
719 #endif
720 #if defined NETWORK_WRITE_USE_SOLARIS_SENDFILEV
721 "\t+ solaris-sendfilev\n"
722 #else
723 "\t- solaris-sendfilev\n"
724 #endif
725 #if defined NETWORK_WRITE_USE_WRITEV
726 "\t+ writev\n"
727 #else
728 "\t- writev\n"
729 #endif
730 "\t+ write\n"
731 #ifdef NETWORK_WRITE_USE_MMAP
732 "\t+ mmap support\n"
733 #else
734 "\t- mmap support\n"
735 #endif