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 uintptr, 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, int(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(int(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, int(src), &pos, size)
50 if n > 0 {
51 ignoringEINTR2(func() (int64, error) {
52 return syscall.Seek(int(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, uintptr(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