Source file src/internal/poll/copy_file_range_linux.go

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

View as plain text