copy_file_range_linux.mx raw

   1  // Copyright 2020 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  package poll
   6  
   7  import (
   8  	"internal/syscall/unix"
   9  	"sync"
  10  	"syscall"
  11  )
  12  
  13  func supportCopyFileRange() bool {
  14  	return isKernelVersionGE53()
  15  }
  16  
  17  var isKernelVersionGE53 = sync.OnceValue(func() bool {
  18  	major, minor := unix.KernelVersion()
  19  	// copy_file_range(2) is broken in various ways on kernels older than 5.3,
  20  	// see https://go.dev/issue/42400 and
  21  	// https://man7.org/linux/man-pages/man2/copy_file_range.2.html#VERSIONS
  22  	return major > 5 || (major == 5 && minor >= 3)
  23  })
  24  
  25  // For best performance, call copy_file_range() with the largest len value
  26  // possible. Linux sets up a limitation of data transfer for most of its I/O
  27  // system calls, as MAX_RW_COUNT (INT_MAX & PAGE_MASK). This value equals to
  28  // the maximum integer value minus a page size that is typically 2^12=4096 bytes.
  29  // That is to say, it's the maximum integer value with the lowest 12 bits unset,
  30  // which is 0x7ffff000.
  31  const maxCopyFileRangeRound = 0x7ffff000
  32  
  33  func handleCopyFileRangeErr(err error, copied, written int64) (bool, error) {
  34  	switch err {
  35  	case syscall.ENOSYS:
  36  		// copy_file_range(2) was introduced in Linux 4.5.
  37  		// Go supports Linux >= 3.2, so the system call
  38  		// may not be present.
  39  		//
  40  		// If we see ENOSYS, we have certainly not transferred
  41  		// any data, so we can tell the caller that we
  42  		// couldn't handle the transfer and let them fall
  43  		// back to more generic code.
  44  		return false, nil
  45  	case syscall.EXDEV, syscall.EINVAL, syscall.EIO, syscall.EOPNOTSUPP, syscall.EPERM:
  46  		// Prior to Linux 5.3, it was not possible to
  47  		// copy_file_range across file systems. Similarly to
  48  		// the ENOSYS case above, if we see EXDEV, we have
  49  		// not transferred any data, and we can let the caller
  50  		// fall back to generic code.
  51  		//
  52  		// As for EINVAL, that is what we see if, for example,
  53  		// dst or src refer to a pipe rather than a regular
  54  		// file. This is another case where no data has been
  55  		// transferred, so we consider it unhandled.
  56  		//
  57  		// If src and dst are on CIFS, we can see EIO.
  58  		// See issue #42334.
  59  		//
  60  		// If the file is on NFS, we can see EOPNOTSUPP.
  61  		// See issue #40731.
  62  		//
  63  		// If the process is running inside a Docker container,
  64  		// we might see EPERM instead of ENOSYS. See issue
  65  		// #40893. Since EPERM might also be a legitimate error,
  66  		// don't mark copy_file_range(2) as unsupported.
  67  		return false, nil
  68  	case nil:
  69  		if copied == 0 {
  70  			// Prior to Linux 5.19
  71  			// (https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=868f9f2f8e004bfe0d3935b1976f625b2924893b),
  72  			// copy_file_range can silently fail by reporting
  73  			// success and 0 bytes written. Assume such cases are
  74  			// failure and fallback to a different copy mechanism.
  75  			if written == 0 {
  76  				return false, nil
  77  			}
  78  
  79  			// Otherwise src is at EOF, which means
  80  			// we are done.
  81  		}
  82  	}
  83  	return true, err
  84  }
  85