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