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 that 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 // We want to handle ENOTSUP like EOPNOTSUPP. 120 // It's a pain to put it as a switch case 121 // because on Linux systems ENOTSUP == EOPNOTSUPP, 122 // so the compiler complains about a duplicate case. 123 if err == syscall.ENOTSUP { 124 return written, err, written > 0 125 } 126 127 // Not a retryable error. 128 return written, err, true 129 } 130 } 131 } 132 133 func sendFileChunk(dst, src int, offset *int64, size int, written int64) (n int, err error) { 134 switch runtime.GOOS { 135 case "linux", "android": 136 // The offset is always nil on Linux. 137 n, err = syscall.Sendfile(dst, src, offset, size) 138 case "solaris", "illumos": 139 // Trust the offset, not the return value from sendfile. 140 start := *offset 141 n, err = syscall.Sendfile(dst, src, offset, size) 142 n = int(*offset - start) 143 // A quirk on Solaris/illumos: sendfile claims to support out_fd 144 // as a regular file but returns EINVAL when the out_fd 145 // is not a socket of SOCK_STREAM, while it actually sends 146 // out data anyway and updates the file offset. 147 // 148 // Another quirk: sendfile transfers data and returns EINVAL when being 149 // asked to transfer bytes more than the actual file size. For instance, 150 // the source file is wrapped in an io.LimitedReader with larger size 151 // than the actual file size. 152 // 153 // To handle these cases we ignore EINVAL if any call to sendfile was 154 // able to send data. 155 if err == syscall.EINVAL && (n > 0 || written > 0) { 156 err = nil 157 } 158 default: 159 start := *offset 160 n, err = syscall.Sendfile(dst, src, offset, size) 161 if n > 0 { 162 // The BSD implementations of syscall.Sendfile don't 163 // update the offset parameter (despite it being a *int64). 164 // 165 // Trust the return value from sendfile, not the offset. 166 *offset = start + int64(n) 167 } 168 } 169 return 170 } 171