xref: /lighttpd1.4/src/network_write.c (revision 86c2d309)
1 #include "first.h"
2 
3 #include "network_write.h"
4 
5 #include "base.h"
6 #include "ck.h"
7 #include "log.h"
8 
9 #include <sys/types.h>
10 #include "sys-socket.h"
11 
12 #include <errno.h>
13 #include <string.h>
14 #include <unistd.h>
15 
16 
17 /* on linux 2.4.x you get either sendfile or LFS */
18 #if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE \
19  && (!defined _LARGEFILE_SOURCE || defined HAVE_SENDFILE64) \
20  && defined(__linux__) && !defined HAVE_SENDFILE_BROKEN
21 # ifdef NETWORK_WRITE_USE_SENDFILE
22 #  error "can't have more than one sendfile implementation"
23 # endif
24 # define NETWORK_WRITE_USE_SENDFILE "linux-sendfile"
25 # define NETWORK_WRITE_USE_LINUX_SENDFILE
26 #endif
27 
28 #if defined HAVE_SENDFILE && (defined(__FreeBSD__) || defined(__DragonFly__))
29 # ifdef NETWORK_WRITE_USE_SENDFILE
30 #  error "can't have more than one sendfile implementation"
31 # endif
32 # define NETWORK_WRITE_USE_SENDFILE "freebsd-sendfile"
33 # define NETWORK_WRITE_USE_FREEBSD_SENDFILE
34 #endif
35 
36 #if defined HAVE_SENDFILE && defined(__APPLE__)
37 # ifdef NETWORK_WRITE_USE_SENDFILE
38 #  error "can't have more than one sendfile implementation"
39 # endif
40 # define NETWORK_WRITE_USE_SENDFILE "darwin-sendfile"
41 # define NETWORK_WRITE_USE_DARWIN_SENDFILE
42 #endif
43 
44 #if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILEV && defined(__sun)
45 # ifdef NETWORK_WRITE_USE_SENDFILE
46 #  error "can't have more than one sendfile implementation"
47 # endif
48 # define NETWORK_WRITE_USE_SENDFILE "solaris-sendfilev"
49 # define NETWORK_WRITE_USE_SOLARIS_SENDFILEV
50 #endif
51 
52 /* not supported so far
53 #if defined HAVE_SEND_FILE && defined(__aix)
54 # ifdef NETWORK_WRITE_USE_SENDFILE
55 #  error "can't have more than one sendfile implementation"
56 # endif
57 # define NETWORK_WRITE_USE_SENDFILE "aix-sendfile"
58 # define NETWORK_WRITE_USE_AIX_SENDFILE
59 #endif
60 */
61 
62 #if defined HAVE_SYS_UIO_H && defined HAVE_WRITEV
63 # define NETWORK_WRITE_USE_WRITEV
64 #endif
65 
66 #if defined HAVE_SYS_MMAN_H && defined HAVE_MMAP && defined ENABLE_MMAP
67 # define NETWORK_WRITE_USE_MMAP
68 #endif
69 
70 
71 __attribute_cold__
72 static int network_write_error(int fd, log_error_st *errh) {
73   #if defined(__WIN32)
74     int lastError = WSAGetLastError();
75     switch (lastError) {
76       case WSAEINTR:
77       case WSAEWOULDBLOCK:
78         return -3;
79       case WSAECONNRESET:
80       case WSAETIMEDOUT:
81       case WSAECONNABORTED:
82         return -2;
83       default:
84         log_error(errh,__FILE__,__LINE__,"send failed: %d %d",lastError,fd);
85         return -1;
86     }
87   #else /* __WIN32 */
88     switch (errno) {
89       case EAGAIN:
90       case EINTR:
91         return -3;
92       case EPIPE:
93       case ECONNRESET:
94         return -2;
95       default:
96         log_perror(errh,__FILE__,__LINE__,"write failed: %d",fd);
97         return -1;
98     }
99   #endif /* __WIN32 */
100 }
101 
102 __attribute_cold__
103 static int network_remove_finished_chunks(chunkqueue * const cq, const off_t len) {
104     force_assert(len >= 0);
105     chunkqueue_remove_finished_chunks(cq);
106     return 0;
107 }
108 
109 inline
110 static ssize_t network_write_data_len(int fd, const char *data, off_t len) {
111   #if defined(__WIN32)
112     return send(fd, data, len, 0);
113   #else /* __WIN32 */
114     return write(fd, data, len);
115   #endif /* __WIN32 */
116 }
117 
118 static int network_write_accounting(const int fd, chunkqueue * const cq, off_t * const p_max_bytes, log_error_st * const errh, const ssize_t wr, const off_t toSend) {
119     if (wr >= 0) {
120         *p_max_bytes -= wr;/*(toSend > 0 if we reach this func)*/
121         const int rc = (wr == toSend && *p_max_bytes > 0) ? 0 : -3;
122         chunkqueue_mark_written(cq, wr);
123         return rc;
124     }
125     else
126         return network_write_error(fd, errh);
127 }
128 
129 
130 
131 
132 /* write next chunk(s); finished chunks are removed afterwards after successful writes.
133  * return values: similar as backends (0 success, -1 error, -2 remote close, -3 try again later (EINTR/EAGAIN)) */
134 /* next chunk must be MEM_CHUNK. use write()/send() */
135 #if !defined(NETWORK_WRITE_USE_WRITEV)
136 static int network_write_mem_chunk(const int fd, chunkqueue * const cq, off_t * const p_max_bytes, log_error_st * const errh) {
137     chunk* const c = cq->first;
138     off_t c_len = (off_t)buffer_clen(c->mem) - c->offset;
139     if (c_len > *p_max_bytes) c_len = *p_max_bytes;
140     if (c_len <= 0) return network_remove_finished_chunks(cq, c_len);
141 
142     ssize_t wr = network_write_data_len(fd, c->mem->ptr + c->offset, c_len);
143     return network_write_accounting(fd, cq, p_max_bytes, errh, wr, c_len);
144 }
145 #endif
146 
147 
148 
149 
150 #if !defined(NETWORK_WRITE_USE_MMAP)
151 
152 static int network_write_file_chunk_no_mmap(const int fd, chunkqueue * const cq, off_t * const p_max_bytes, log_error_st * const errh) {
153     chunk* const c = cq->first;
154     off_t offset, toSend;
155     char buf[16384]; /* max read 16kb in one step */
156 
157     offset = c->offset;
158     toSend = c->file.length - c->offset;
159     if (toSend > *p_max_bytes) toSend = *p_max_bytes;
160     if (toSend <= 0) return network_remove_finished_chunks(cq, toSend);
161 
162     if (c->file.fd < 0 && 0 != chunkqueue_open_file_chunk(cq, errh)) return -1;
163 
164     if (toSend > (off_t)sizeof(buf)) toSend = (off_t)sizeof(buf);
165 
166   #ifndef HAVE_PREAD
167     if (-1 == lseek(c->file.fd, offset, SEEK_SET)) {
168         log_perror(errh, __FILE__, __LINE__, "lseek");
169         return -1;
170     }
171     toSend = read(c->file.fd, buf, toSend);
172   #else
173     toSend =pread(c->file.fd, buf, toSend, offset);
174   #endif
175     if (toSend <= 0) {
176         log_perror(errh, __FILE__, __LINE__, "read");/* err or unexpected EOF */
177         return -1;
178     }
179 
180     ssize_t wr = network_write_data_len(fd, buf, toSend);
181     return network_write_accounting(fd, cq, p_max_bytes, errh, wr, toSend);
182 }
183 
184 #endif
185 
186 
187 
188 
189 #if defined(NETWORK_WRITE_USE_MMAP)
190 
191 #include "sys-mmap.h"
192 
193 #include <setjmp.h>
194 #include <signal.h>
195 
196 #define MMAP_CHUNK_SIZE (512*1024)
197 
198 static off_t mmap_align_offset(off_t start) {
199     static long pagesize = 0;
200     if (0 == pagesize) {
201         pagesize = sysconf(_SC_PAGESIZE);
202         force_assert(pagesize < MMAP_CHUNK_SIZE);
203     }
204     force_assert(start >= (start % pagesize));
205     return start - (start % pagesize);
206 }
207 
208 static volatile int sigbus_jmp_valid;
209 static sigjmp_buf sigbus_jmp;
210 
211 static void sigbus_handler(int sig) {
212     UNUSED(sig);
213     if (sigbus_jmp_valid) siglongjmp(sigbus_jmp, 1);
214     ck_bt_abort(__FILE__, __LINE__, "SIGBUS");
215 }
216 
217 /* next chunk must be FILE_CHUNK. send mmap()ed file with write() */
218 static int network_write_file_chunk_mmap(const int fd, chunkqueue * const cq, off_t * const p_max_bytes, log_error_st * const errh) {
219     chunk* const c = cq->first;
220     off_t offset, toSend, file_end;
221     size_t mmap_offset, mmap_avail;
222     const char *data;
223 
224     file_end = c->file.length; /*file end offset in this chunk*/
225     offset = c->offset;
226     toSend = c->file.length - c->offset;
227     if (toSend > *p_max_bytes) toSend = *p_max_bytes;
228     if (toSend <= 0) return network_remove_finished_chunks(cq, toSend);
229 
230     if (c->file.fd < 0 && 0 != chunkqueue_open_file_chunk(cq, errh)) return -1;
231 
232     /* mmap buffer if offset is outside old mmap area or not mapped at all */
233     if (MAP_FAILED == c->file.mmap.start
234         || offset < c->file.mmap.offset
235         || offset >= (off_t)(c->file.mmap.offset + c->file.mmap.length)) {
236 
237         if (MAP_FAILED != c->file.mmap.start) {
238             munmap(c->file.mmap.start, c->file.mmap.length);
239             c->file.mmap.start = MAP_FAILED;
240         }
241 
242         /* Optimizations for the future:
243          *
244          * adaptive mem-mapping
245          *   the problem:
246          *     we mmap() the whole file. If someone has a lot of large files and
247          *     32-bit machine the virtual address area will be exhausted and we
248          *     will have a failing mmap() call.
249          *   solution:
250          *     only mmap 16M in one chunk and move the window as soon as we have
251          *     finished the first 8M
252          *
253          * read-ahead buffering
254          *   the problem:
255          *     sending out several large files in parallel trashes read-ahead
256          *     of the kernel leading to long wait-for-seek times.
257          *   solutions: (increasing complexity)
258          *     1. use madvise
259          *     2. use a internal read-ahead buffer in the chunk-structure
260          *     3. use non-blocking IO for file-transfers
261          *   */
262 
263         c->file.mmap.offset = mmap_align_offset(offset);
264 
265         /* all mmap()ed areas are MMAP_CHUNK_SIZE
266          * except the last which might be smaller */
267         c->file.mmap.length = MMAP_CHUNK_SIZE;
268         if (c->file.mmap.offset > file_end - (off_t)c->file.mmap.length) {
269             c->file.mmap.length = file_end - c->file.mmap.offset;
270         }
271 
272         c->file.mmap.start = mmap(NULL, c->file.mmap.length, PROT_READ,
273                                   MAP_SHARED, c->file.fd, c->file.mmap.offset);
274         if (MAP_FAILED == c->file.mmap.start) {
275             log_perror(errh, __FILE__, __LINE__,
276               "mmap failed: %s %d %lld %zu", c->mem->ptr, c->file.fd,
277               (long long)c->file.mmap.offset, c->file.mmap.length);
278             return -1;
279         }
280 
281       #if defined(HAVE_MADVISE)
282         /* don't advise files < 64Kb */
283         if (c->file.mmap.length > (64*1024)) {
284             /* darwin 7 is returning EINVAL all the time and I don't know how to
285              * detect this at runtime.
286              *
287              * ignore the return value for now */
288             madvise(c->file.mmap.start, c->file.mmap.length, MADV_WILLNEED);
289         }
290       #endif
291     }
292 
293     force_assert(offset >= c->file.mmap.offset);
294     mmap_offset = offset - c->file.mmap.offset;
295     force_assert(c->file.mmap.length > mmap_offset);
296     mmap_avail = c->file.mmap.length - mmap_offset;
297     if (toSend > (off_t) mmap_avail) toSend = mmap_avail;
298 
299     data = c->file.mmap.start + mmap_offset;
300 
301     /* setup SIGBUS handler, but don't activate sigbus_jmp_valid yet */
302     if (0 == sigsetjmp(sigbus_jmp, 1)) {
303         signal(SIGBUS, sigbus_handler);
304 
305         sigbus_jmp_valid = 1;
306         ssize_t wr = network_write_data_len(fd, data, toSend);
307         sigbus_jmp_valid = 0;
308         return network_write_accounting(fd, cq, p_max_bytes, errh, wr, toSend);
309     } else {
310         sigbus_jmp_valid = 0;
311 
312         log_error(errh, __FILE__, __LINE__,
313           "SIGBUS in mmap: %s %d", c->mem->ptr, c->file.fd);
314 
315         munmap(c->file.mmap.start, c->file.mmap.length);
316         c->file.mmap.start = MAP_FAILED;
317         return -1;
318     }
319 
320 }
321 
322 #endif /* NETWORK_WRITE_USE_MMAP */
323 
324 
325 
326 
327 #if defined(NETWORK_WRITE_USE_WRITEV)
328 
329 #if defined(HAVE_SYS_UIO_H)
330 # include <sys/uio.h>
331 #endif
332 
333 #if defined(UIO_MAXIOV)
334 # define SYS_MAX_CHUNKS UIO_MAXIOV
335 #elif defined(IOV_MAX)
336 /* new name for UIO_MAXIOV since IEEE Std 1003.1-2001 */
337 # define SYS_MAX_CHUNKS IOV_MAX
338 #elif defined(_XOPEN_IOV_MAX)
339 /* minimum value for sysconf(_SC_IOV_MAX); posix requires this to be at least 16, which is good enough - no need to call sysconf() */
340 # define SYS_MAX_CHUNKS _XOPEN_IOV_MAX
341 #else
342 # error neither UIO_MAXIOV nor IOV_MAX nor _XOPEN_IOV_MAX are defined
343 #endif
344 
345 /* allocate iovec[MAX_CHUNKS] on stack, so pick a sane limit:
346  * - each entry will use 1 pointer + 1 size_t
347  * - 32 chunks -> 256 / 512 bytes (32-bit/64-bit pointers)
348  */
349 #define STACK_MAX_ALLOC_CHUNKS 32
350 #if SYS_MAX_CHUNKS > STACK_MAX_ALLOC_CHUNKS
351 # define MAX_CHUNKS STACK_MAX_ALLOC_CHUNKS
352 #else
353 # define MAX_CHUNKS SYS_MAX_CHUNKS
354 #endif
355 
356 /* next chunk must be MEM_CHUNK. send multiple mem chunks using writev() */
357 static int network_writev_mem_chunks(const int fd, chunkqueue * const cq, off_t * const p_max_bytes, log_error_st * const errh) {
358     size_t num_chunks = 0;
359     off_t toSend = 0;
360     struct iovec chunks[MAX_CHUNKS];
361 
362     for (const chunk *c = cq->first; c && MEM_CHUNK == c->type; c = c->next) {
363         const off_t c_len = (off_t)buffer_clen(c->mem) - c->offset;
364         if (c_len > 0) {
365             toSend += c_len;
366 
367             chunks[num_chunks].iov_base = c->mem->ptr + c->offset;
368             chunks[num_chunks].iov_len = (size_t)c_len;
369 
370             if (++num_chunks == MAX_CHUNKS || toSend >= *p_max_bytes) break;
371         }
372         else if (c_len < 0) /*(should not happen; trigger assert)*/
373             return network_remove_finished_chunks(cq, c_len);
374     }
375     if (0 == num_chunks) return network_remove_finished_chunks(cq, 0);
376 
377     ssize_t wr = writev(fd, chunks, num_chunks);
378     return network_write_accounting(fd, cq, p_max_bytes, errh, wr, toSend);
379 }
380 
381 #endif /* NETWORK_WRITE_USE_WRITEV */
382 
383 
384 
385 
386 #if defined(NETWORK_WRITE_USE_SENDFILE)
387 
388 #if defined(NETWORK_WRITE_USE_LINUX_SENDFILE) \
389  || defined(NETWORK_WRITE_USE_SOLARIS_SENDFILEV)
390 #include <sys/sendfile.h>
391 #endif
392 
393 #if defined(NETWORK_WRITE_USE_FREEBSD_SENDFILE) \
394  || defined(NETWORK_WRITE_USE_DARWIN_SENDFILE)
395 #include <sys/uio.h>
396 #endif
397 
398 static int network_write_file_chunk_sendfile(const int fd, chunkqueue * const cq, off_t * const p_max_bytes, log_error_st * const errh) {
399     chunk * const c = cq->first;
400     ssize_t wr;
401     off_t offset;
402     off_t toSend;
403     off_t written = 0;
404 
405     offset = c->offset;
406     toSend = c->file.length - c->offset;
407     if (toSend > *p_max_bytes) toSend = *p_max_bytes;
408     if (toSend <= 0) return network_remove_finished_chunks(cq, toSend);
409 
410     if (c->file.fd < 0 && 0 != chunkqueue_open_file_chunk(cq, errh)) return -1;
411 
412     /* Darwin, FreeBSD, and Solaris variants support iovecs and could
413      * be optimized to send more than just file in single syscall */
414 
415   #if defined(NETWORK_WRITE_USE_LINUX_SENDFILE)
416 
417     wr = sendfile(fd, c->file.fd, &offset, toSend);
418     if (wr > 0) written = (off_t)wr;
419 
420   #elif defined(NETWORK_WRITE_USE_DARWIN_SENDFILE)
421 
422     written = toSend;
423     wr = sendfile(c->file.fd, fd, offset, &written, NULL, 0);
424     /* (for EAGAIN/EINTR written still contains the sent bytes) */
425 
426   #elif defined(NETWORK_WRITE_USE_FREEBSD_SENDFILE)
427 
428     wr = sendfile(c->file.fd, fd, offset, toSend, NULL, &written, 0);
429     /* (for EAGAIN/EINTR written still contains the sent bytes) */
430 
431   #elif defined(NETWORK_WRITE_USE_SOLARIS_SENDFILEV)
432     {
433         sendfilevec_t fvec;
434         fvec.sfv_fd = c->file.fd;
435         fvec.sfv_flag = 0;
436         fvec.sfv_off = offset;
437         fvec.sfv_len = toSend;
438 
439         /* Solaris sendfilev() */
440         wr = sendfilev(fd, &fvec, 1, (size_t *)&written);
441         /* (for EAGAIN/EINTR written still contains the sent bytes) */
442     }
443   #else
444 
445     wr = -1;
446     errno = ENOSYS;
447 
448   #endif
449 
450     if (-1 == wr) {
451         switch(errno) {
452           case EAGAIN:
453           case EINTR:
454             break; /* try again later */
455           case EPIPE:
456           case ECONNRESET:
457           case ENOTCONN:
458             return -2;
459           case EINVAL:
460           case ENOSYS:
461          #if defined(ENOTSUP) && (!defined(EOPNOTSUPP) || EOPNOTSUPP != ENOTSUP)
462           case ENOTSUP:
463          #endif
464          #ifdef EOPNOTSUPP
465           case EOPNOTSUPP:
466          #endif
467          #ifdef ESOCKTNOSUPPORT
468           case ESOCKTNOSUPPORT:
469          #endif
470          #ifdef EAFNOSUPPORT
471           case EAFNOSUPPORT:
472          #endif
473            #ifdef NETWORK_WRITE_USE_MMAP
474             return network_write_file_chunk_mmap(fd, cq, p_max_bytes, errh);
475            #else
476             return network_write_file_chunk_no_mmap(fd, cq, p_max_bytes, errh);
477            #endif
478           default:
479             log_perror(errh, __FILE__, __LINE__, "sendfile(): fd: %d", fd);
480             return -1;
481         }
482     }
483 
484     if (written > 0) {
485         chunkqueue_mark_written(cq, written);
486         *p_max_bytes -= written;
487         if (__builtin_expect( (*p_max_bytes <= 0), 0)) return -3;
488     }
489     else if (0 == wr) { /*(-1 != wr && 0 == written)*/
490         log_error(errh, __FILE__, __LINE__,
491                   "sendfile(): fd: %d file truncated", fd);
492         return -1;
493     }
494 
495     return (wr >= 0 && written == toSend) ? 0 : -3;
496 }
497 
498 #endif
499 
500 
501 
502 
503 /* return values:
504  * >= 0 : no error
505  *   -1 : error (on our side)
506  *   -2 : remote close
507  */
508 
509 static int network_write_chunkqueue_writev(const int fd, chunkqueue * const cq, off_t max_bytes, log_error_st * const errh) {
510     while (NULL != cq->first) {
511         int rc = -1;
512 
513         switch (cq->first->type) {
514         case MEM_CHUNK:
515           #if defined(NETWORK_WRITE_USE_WRITEV)
516             rc = network_writev_mem_chunks(fd, cq, &max_bytes, errh);
517           #else
518             rc = network_write_mem_chunk(fd, cq, &max_bytes, errh);
519           #endif
520             break;
521         case FILE_CHUNK:
522           #ifdef NETWORK_WRITE_USE_MMAP
523             rc = network_write_file_chunk_mmap(fd, cq, &max_bytes, errh);
524           #else
525             rc = network_write_file_chunk_no_mmap(fd, cq, &max_bytes, errh);
526           #endif
527             break;
528         }
529 
530         if (__builtin_expect( (0 != rc), 0)) return (-3 == rc) ? 0 : rc;
531     }
532 
533     return 0;
534 }
535 
536 #if defined(NETWORK_WRITE_USE_SENDFILE)
537 static int network_write_chunkqueue_sendfile(const int fd, chunkqueue * const cq, off_t max_bytes, log_error_st * const errh) {
538     while (NULL != cq->first) {
539         int rc = -1;
540 
541         switch (cq->first->type) {
542         case MEM_CHUNK:
543           #if defined(NETWORK_WRITE_USE_WRITEV)
544             rc = network_writev_mem_chunks(fd, cq, &max_bytes, errh);
545           #else
546             rc = network_write_mem_chunk(fd, cq, &max_bytes, errh);
547           #endif
548             break;
549         case FILE_CHUNK:
550           #if defined(NETWORK_WRITE_USE_SENDFILE)
551             rc = network_write_file_chunk_sendfile(fd, cq, &max_bytes, errh);
552           #elif defined(NETWORK_WRITE_USE_MMAP)
553             rc = network_write_file_chunk_mmap(fd, cq, &max_bytes, errh);
554           #else
555             rc = network_write_file_chunk_no_mmap(fd, cq, &max_bytes, errh);
556           #endif
557             break;
558         }
559 
560         if (__builtin_expect( (0 != rc), 0)) return (-3 == rc) ? 0 : rc;
561     }
562 
563     return 0;
564 }
565 #endif
566 
567 int network_write_init(server *srv) {
568     typedef enum {
569         NETWORK_BACKEND_UNSET,
570         NETWORK_BACKEND_WRITE,
571         NETWORK_BACKEND_WRITEV,
572         NETWORK_BACKEND_SENDFILE,
573     } network_backend_t;
574 
575     network_backend_t backend;
576 
577     struct nb_map {
578         network_backend_t nb;
579         const char *name;
580     } network_backends[] = {
581         /* lowest id wins */
582         { NETWORK_BACKEND_SENDFILE, "sendfile" },
583         { NETWORK_BACKEND_SENDFILE, "linux-sendfile" },
584         { NETWORK_BACKEND_SENDFILE, "freebsd-sendfile" },
585         { NETWORK_BACKEND_SENDFILE, "solaris-sendfilev" },
586         { NETWORK_BACKEND_WRITEV,   "writev" },
587         { NETWORK_BACKEND_WRITE,    "write" },
588         { NETWORK_BACKEND_UNSET,    NULL }
589     };
590 
591     /* get a useful default */
592     backend = network_backends[0].nb;
593 
594     /* match name against known types */
595     if (srv->srvconf.network_backend) {
596         const char *name, *confname = srv->srvconf.network_backend->ptr;
597         for (size_t i = 0; NULL != (name = network_backends[i].name); ++i) {
598             if (0 == strcmp(confname, name)) {
599                 backend = network_backends[i].nb;
600                 break;
601             }
602         }
603         if (NULL == name) {
604             log_error(srv->errh, __FILE__, __LINE__,
605               "server.network-backend has an unknown value: %s", confname);
606             return -1;
607         }
608     }
609 
610     switch(backend) {
611     case NETWORK_BACKEND_SENDFILE:
612       #if defined(NETWORK_WRITE_USE_SENDFILE)
613         srv->network_backend_write = network_write_chunkqueue_sendfile;
614         break;
615       #endif
616     case NETWORK_BACKEND_WRITEV:
617     case NETWORK_BACKEND_WRITE:
618         srv->network_backend_write = network_write_chunkqueue_writev;
619         break;
620     default:
621         return -1;
622     }
623 
624     return 0;
625 }
626 
627 const char * network_write_show_handlers(void) {
628     return
629       "\nNetwork handler:\n\n"
630      #if defined NETWORK_WRITE_USE_LINUX_SENDFILE
631       "\t+ linux-sendfile\n"
632      #else
633       "\t- linux-sendfile\n"
634      #endif
635      #if defined NETWORK_WRITE_USE_FREEBSD_SENDFILE
636       "\t+ freebsd-sendfile\n"
637      #else
638       "\t- freebsd-sendfile\n"
639      #endif
640      #if defined NETWORK_WRITE_USE_DARWIN_SENDFILE
641       "\t+ darwin-sendfile\n"
642      #else
643       "\t- darwin-sendfile\n"
644      #endif
645      #if defined NETWORK_WRITE_USE_SOLARIS_SENDFILEV
646       "\t+ solaris-sendfilev\n"
647      #else
648       "\t- solaris-sendfilev\n"
649      #endif
650      #if defined NETWORK_WRITE_USE_WRITEV
651       "\t+ writev\n"
652      #else
653       "\t- writev\n"
654      #endif
655       "\t+ write\n"
656      #ifdef NETWORK_WRITE_USE_MMAP
657       "\t+ mmap support\n"
658      #else
659       "\t- mmap support\n"
660      #endif
661       ;
662 }
663