Source File
sendfile_unix.go
Belonging Package
internal/poll
// 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 (
)
// 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 ( *FD, int, int64) ( int64, error, bool) {
if := runtime.GOOS; == "linux" || == "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(, , nil, )
}
// 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.
, := ignoringEINTR2(func() (int64, error) {
return syscall.Seek(, 0, io.SeekCurrent)
})
if != nil {
return 0, , false
}
:=
, , = sendFile(, , &, )
if > 0 {
ignoringEINTR2(func() (int64, error) {
return syscall.Seek(, +, io.SeekStart)
})
}
return , ,
}
// sendFile wraps the sendfile system call.
func sendFile( *FD, int, *int64, int64) ( int64, error, bool) {
defer func() {
TestHookDidSendFile(, , , , )
}()
if := .writeLock(); != nil {
return 0, , false
}
defer .writeUnlock()
if := .pd.prepareWrite(.isFile); != nil {
return 0, , false
}
:= .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.
:= 1<<31 - 1
if > 0 {
= int(min(-, int64()))
}
var int
, = sendFileChunk(, , , , )
if > 0 {
+= int64()
}
switch {
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 == 0 || ( > 0 && >= ) {
return , 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 > 0 && >= {
return , nil, true
}
if = .pd.waitWrite(.isFile); != nil {
return , , 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 , , > 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 == syscall.ENOTSUP {
return , , > 0
}
// Not a retryable error.
return , , true
}
}
}
func sendFileChunk(, int, *int64, int, int64) ( int, error) {
switch runtime.GOOS {
case "linux", "android":
// The offset is always nil on Linux.
, = syscall.Sendfile(, , , )
case "solaris", "illumos":
// Trust the offset, not the return value from sendfile.
:= *
, = syscall.Sendfile(, , , )
= int(* - )
// 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 == syscall.EINVAL && ( > 0 || > 0) {
= nil
}
default:
:= *
, = syscall.Sendfile(, , , )
if > 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.
* = + int64()
}
}
return
}
The pages are generated with Golds v0.7.3-preview. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |