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