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

View as plain text