| // Copyright 2024 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| //go:build darwin || dragonfly || freebsd || linux || solaris | |
| package poll | |
| import ( | |
| "io" | |
| "runtime" | |
| "syscall" | |
| ) | |
| // SendFile wraps the sendfile system call. | |
| // | |
| // It copies data from src (a file descriptor) to dstFD, | |
| // starting at the current position of src. | |
| // It updates the current position of src to after the | |
| // copied data. | |
| // | |
| // If size is zero, it copies the rest of src. | |
| // Otherwise, it copies up to size bytes. | |
| // | |
| // The handled return parameter indicates whether SendFile | |
| // was able to handle some or all of the operation. | |
| // If handled is false, sendfile was unable to perform the copy, | |
| // has not modified the source or destination, | |
| // and the caller should perform the copy using a fallback implementation. | |
| func SendFile(dstFD *FD, src uintptr, size int64) (n int64, err error, handled bool) { | |
| if goos := runtime.GOOS; goos == "linux" || goos == "android" { | |
| // Linux's sendfile doesn't require any setup: | |
| // It sends from the current position of the source file and | |
| // updates the position of the source after sending. | |
| return sendFile(dstFD, int(src), nil, size) | |
| } | |
| // Non-Linux sendfile implementations don't use the current position of the source file, | |
| // so we need to look up the position, pass it explicitly, and adjust it after | |
| // sendfile returns. | |
| start, err := ignoringEINTR2(func() (int64, error) { | |
| return syscall.Seek(int(src), 0, io.SeekCurrent) | |
| }) | |
| if err != nil { | |
| return 0, err, false | |
| } | |
| pos := start | |
| n, err, handled = sendFile(dstFD, int(src), &pos, size) | |
| if n > 0 { | |
| ignoringEINTR2(func() (int64, error) { | |
| return syscall.Seek(int(src), start+n, io.SeekStart) | |
| }) | |
| } | |
| return n, err, handled | |
| } | |
| // sendFile wraps the sendfile system call. | |
| func sendFile(dstFD *FD, src int, offset *int64, size int64) (written int64, err error, handled bool) { | |
| defer func() { | |
| TestHookDidSendFile(dstFD, uintptr(src), written, err, handled) | |
| }() | |
| if err := dstFD.writeLock(); err != nil { | |
| return 0, err, false | |
| } | |
| defer dstFD.writeUnlock() | |
| if err := dstFD.pd.prepareWrite(dstFD.isFile); err != nil { | |
| return 0, err, false | |
| } | |
| dst := dstFD.Sysfd | |
| for { | |
| // Some platforms support passing 0 to read to the end of the source, | |
| // but all platforms support just writing a large value. | |
| // | |
| // Limit the maximum size to fit in an int32, to avoid any possible overflow. | |
| chunk := 1<<31 - 1 | |
| if size > 0 { | |
| chunk = int(min(size-written, int64(chunk))) | |
| } | |
| var n int | |
| n, err = sendFileChunk(dst, src, offset, chunk, written) | |
| if n > 0 { | |
| written += int64(n) | |
| } | |
| switch err { | |
| case nil: | |
| // We're done if sendfile copied no bytes | |
| // (we're at the end of the source) | |
| // or if we have a size limit and have reached it. | |
| // | |
| // If sendfile copied some bytes and we don't have a size limit, | |
| // try again to see if there is more data to copy. | |
| if n == 0 || (size > 0 && written >= size) { | |
| return written, nil, true | |
| } | |
| case syscall.EAGAIN: | |
| // *BSD and Darwin can return EAGAIN with n > 0, | |
| // so check to see if the write has completed. | |
| // So far as we know all other platforms only | |
| // return EAGAIN when n == 0, but checking is harmless. | |
| if size > 0 && written >= size { | |
| return written, nil, true | |
| } | |
| if err = dstFD.pd.waitWrite(dstFD.isFile); err != nil { | |
| return written, err, true | |
| } | |
| case syscall.EINTR: | |
| // Retry. | |
| case syscall.ENOSYS, syscall.EOPNOTSUPP, syscall.EINVAL: | |
| // ENOSYS indicates no kernel support for sendfile. | |
| // EINVAL indicates a FD type that does not support sendfile. | |
| // | |
| // On Linux, copy_file_range can return EOPNOTSUPP when copying | |
| // to a NFS file (issue #40731); check for it here just in case. | |
| return written, err, written > 0 | |
| default: | |
| // We want to handle ENOTSUP like EOPNOTSUPP. | |
| // It's a pain to put it as a switch case | |
| // because on Linux systems ENOTSUP == EOPNOTSUPP, | |
| // so the compiler complains about a duplicate case. | |
| if err == syscall.ENOTSUP { | |
| return written, err, written > 0 | |
| } | |
| // Not a retryable error. | |
| return written, err, true | |
| } | |
| } | |
| } | |
| func sendFileChunk(dst, src int, offset *int64, size int, written int64) (n int, err error) { | |
| switch runtime.GOOS { | |
| case "linux", "android": | |
| // The offset is always nil on Linux. | |
| n, err = syscall.Sendfile(dst, src, offset, size) | |
| case "solaris", "illumos": | |
| // Trust the offset, not the return value from sendfile. | |
| start := *offset | |
| n, err = syscall.Sendfile(dst, src, offset, size) | |
| n = int(*offset - start) | |
| // A quirk on Solaris/illumos: sendfile claims to support out_fd | |
| // as a regular file but returns EINVAL when the out_fd | |
| // is not a socket of SOCK_STREAM, while it actually sends | |
| // out data anyway and updates the file offset. | |
| // | |
| // Another quirk: sendfile transfers data and returns EINVAL when being | |
| // asked to transfer bytes more than the actual file size. For instance, | |
| // the source file is wrapped in an io.LimitedReader with larger size | |
| // than the actual file size. | |
| // | |
| // To handle these cases we ignore EINVAL if any call to sendfile was | |
| // able to send data. | |
| if err == syscall.EINVAL && (n > 0 || written > 0) { | |
| err = nil | |
| } | |
| default: | |
| start := *offset | |
| n, err = syscall.Sendfile(dst, src, offset, size) | |
| if n > 0 { | |
| // The BSD implementations of syscall.Sendfile don't | |
| // update the offset parameter (despite it being a *int64). | |
| // | |
| // Trust the return value from sendfile, not the offset. | |
| *offset = start + int64(n) | |
| } | |
| } | |
| return | |
| } | |