// 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
}