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