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