}
n, err := copyFileRange(dst, src, int(max))
switch err {
- case syscall.EINVAL, syscall.EIO, syscall.EOPNOTSUPP, syscall.EPERM:
- // EINVAL is what we see if, for example,
- // dst or src refers to a pipe rather than a regular
+ case syscall.ENOSYS:
+ // copy_file_range(2) was introduced in Linux 4.5.
+ // Go supports Linux >= 2.6.33, so the system call
+ // may not be present.
+ //
+ // If we see ENOSYS, we have certainly not transferred
+ // any data, so we can tell the caller that we
+ // couldn't handle the transfer and let them fall
+ // back to more generic code.
+ return 0, false, nil
+ case syscall.EXDEV, syscall.EINVAL, syscall.EIO, syscall.EOPNOTSUPP, syscall.EPERM:
+ // Prior to Linux 5.3, it was not possible to
+ // copy_file_range across file systems. Similarly to
+ // the ENOSYS case above, if we see EXDEV, we have
+ // not transferred any data, and we can let the caller
+ // fall back to generic code.
+ //
+ // As for EINVAL, that is what we see if, for example,
+ // dst or src refer to a pipe rather than a regular
// file. This is another case where no data has been
// transferred, so we consider it unhandled.
//
// See issue #40731.
//
// If the process is running inside a Docker container,
- // we might see EPERM instead of ENOSYS. See issue #40893.
- // Since EPERM might also be a legitimate error: operation not permitted,
- // we should still keep this error even if we have the previous kernel version 5.3 check
- // and don't mark copy_file_range(2) as unsupported.
+ // we might see EPERM instead of ENOSYS. See issue
+ // #40893. Since EPERM might also be a legitimate error,
+ // don't mark copy_file_range(2) as unsupported.
return 0, false, nil
case nil:
if n == 0 {
if written == 0 {
return 0, false, nil
}
- // Otherwise, src is at EOF, which means
+ // Otherwise src is at EOF, which means
// we are done.
return written, true, nil
}