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