Source file src/internal/poll/sendfile_unix.go
1 // Copyright 2024 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build darwin || dragonfly || freebsd || linux || solaris 6 7 package poll 8 9 import ( 10 "io" 11 "runtime" 12 "syscall" 13 ) 14 15 // SendFile wraps the sendfile system call. 16 // 17 // It copies data from src (a file descriptor) to dstFD, 18 // starting at the current position of src. 19 // It updates the current position of src to after the 20 // copied data. 21 // 22 // If size is zero, it copies the rest of src. 23 // Otherwise, it copies up to size bytes. 24 // 25 // The handled return parameter indicates whether SendFile 26 // was able to handle some or all of the operation. 27 // If handled is false, sendfile was unable to perform the copy, 28 // has not modified the source or destination, 29 // and the caller should perform the copy using a fallback implementation. 30 func SendFile(dstFD *FD, src int, size int64) (n int64, err error, handled bool) { 31 if goos := runtime.GOOS; goos == "linux" || goos == "android" { 32 // Linux's sendfile doesn't require any setup: 33 // It sends from the current position of the source file and 34 // updates the position of the source after sending. 35 return sendFile(dstFD, src, nil, size) 36 } 37 38 // Non-Linux sendfile implementations don't use the current position of the source file, 39 // so we need to look up the position, pass it explicitly, and adjust it after 40 // sendfile returns. 41 start, err := ignoringEINTR2(func() (int64, error) { 42 return syscall.Seek(src, 0, io.SeekCurrent) 43 }) 44 if err != nil { 45 return 0, err, false 46 } 47 48 pos := start 49 n, err, handled = sendFile(dstFD, src, &pos, size) 50 if n > 0 { 51 ignoringEINTR2(func() (int64, error) { 52 return syscall.Seek(src, start+n, io.SeekStart) 53 }) 54 } 55 return n, err, handled 56 } 57 58 // sendFile wraps the sendfile system call. 59 func sendFile(dstFD *FD, src int, offset *int64, size int64) (written int64, err error, handled bool) { 60 defer func() { 61 TestHookDidSendFile(dstFD, src, written, err, handled) 62 }() 63 if err := dstFD.writeLock(); err != nil { 64 return 0, err, false 65 } 66 defer dstFD.writeUnlock() 67 68 if err := dstFD.pd.prepareWrite(dstFD.isFile); err != nil { 69 return 0, err, false 70 } 71 72 dst := dstFD.Sysfd 73 for { 74 // Some platforms support passing 0 to read to the end of the source, 75 // but all platforms support just writing a large value. 76 // 77 // Limit the maximum size to fit in an int32, to avoid any possible overflow. 78 chunk := 1<<31 - 1 79 if size > 0 { 80 chunk = int(min(size-written, int64(chunk))) 81 } 82 var n int 83 n, err = sendFileChunk(dst, src, offset, chunk, written) 84 if n > 0 { 85 written += int64(n) 86 } 87 switch err { 88 case nil: 89 // We're done if sendfile copied no bytes 90 // (we're at the end of the source) 91 // or if we have a size limit and have reached it. 92 // 93 // If sendfile copied some bytes and we don't have a size limit, 94 // try again to see if there is more data to copy. 95 if n == 0 || (size > 0 && written >= size) { 96 return written, nil, true 97 } 98 case syscall.EAGAIN: 99 // *BSD and Darwin can return EAGAIN with n > 0, 100 // so check to see if the write has completed. 101 // So far as we know all other platforms only 102 // return EAGAIN when n == 0, but checking is harmless. 103 if size > 0 && written >= size { 104 return written, nil, true 105 } 106 if err = dstFD.pd.waitWrite(dstFD.isFile); err != nil { 107 return written, err, true 108 } 109 case syscall.EINTR: 110 // Retry. 111 case syscall.ENOSYS, syscall.EOPNOTSUPP, syscall.EINVAL: 112 // ENOSYS indicates no kernel support for sendfile. 113 // EINVAL indicates a FD type which does not support sendfile. 114 // 115 // On Linux, copy_file_range can return EOPNOTSUPP when copying 116 // to a NFS file (issue #40731); check for it here just in case. 117 return written, err, written > 0 118 default: 119 // Not a retryable error. 120 return written, err, true 121 } 122 } 123 } 124 125 func sendFileChunk(dst, src int, offset *int64, size int, written int64) (n int, err error) { 126 switch runtime.GOOS { 127 case "linux", "android": 128 // The offset is always nil on Linux. 129 n, err = syscall.Sendfile(dst, src, offset, size) 130 case "solaris", "illumos": 131 // Trust the offset, not the return value from sendfile. 132 start := *offset 133 n, err = syscall.Sendfile(dst, src, offset, size) 134 n = int(*offset - start) 135 // A quirk on Solaris/illumos: sendfile claims to support out_fd 136 // as a regular file but returns EINVAL when the out_fd 137 // is not a socket of SOCK_STREAM, while it actually sends 138 // out data anyway and updates the file offset. 139 // 140 // Another quirk: sendfile transfers data and returns EINVAL when being 141 // asked to transfer bytes more than the actual file size. For instance, 142 // the source file is wrapped in an io.LimitedReader with larger size 143 // than the actual file size. 144 // 145 // To handle these cases we ignore EINVAL if any call to sendfile was 146 // able to send data. 147 if err == syscall.EINVAL && (n > 0 || written > 0) { 148 err = nil 149 } 150 default: 151 start := *offset 152 n, err = syscall.Sendfile(dst, src, offset, size) 153 if n > 0 { 154 // The BSD implementations of syscall.Sendfile don't 155 // update the offset parameter (despite it being a *int64). 156 // 157 // Trust the return value from sendfile, not the offset. 158 *offset = start + int64(n) 159 } 160 } 161 return 162 } 163