sendfile_unix.mx raw

   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