Skip to content

Commit 1c7650a

Browse files
committed
internal/poll: use copy_file_range only on Linux kernel >= 5.3
https://man7.org/linux/man-pages/man2/copy_file_range.2.html#VERSIONS states: A major rework of the kernel implementation occurred in 5.3. Areas of the API that weren't clearly defined were clarified and the API bounds are much more strictly checked than on earlier kernels. Applications should target the behaviour and requirements of 5.3 kernels. Rather than attempting to detect the file system for source and destination files (which means two additional statfs syscalls) and skip copy_file_range in case of known defects (e.g. CIFS -> CIFS), just assume copy_file_range to be broken on kernels < 5.3. Fixes #42400 Change-Id: I3a531296182c1d6e341772cc9d2be5bf83e52575 Reviewed-on: https://go-review.googlesource.com/c/go/+/268338 Trust: Tobias Klauser <[email protected]> Run-TryBot: Tobias Klauser <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 1642cd7 commit 1c7650a

File tree

1 file changed

+47
-2
lines changed

1 file changed

+47
-2
lines changed

src/internal/poll/copy_file_range_linux.go

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,60 @@ import (
1010
"syscall"
1111
)
1212

13-
var copyFileRangeSupported int32 = 1 // accessed atomically
13+
var copyFileRangeSupported int32 = -1 // accessed atomically
1414

1515
const maxCopyFileRangeRound = 1 << 30
1616

17+
func kernelVersion() (major int, minor int) {
18+
var uname syscall.Utsname
19+
if err := syscall.Uname(&uname); err != nil {
20+
return
21+
}
22+
23+
rl := uname.Release
24+
var values [2]int
25+
vi := 0
26+
value := 0
27+
for _, c := range rl {
28+
if '0' <= c && c <= '9' {
29+
value = (value * 10) + int(c-'0')
30+
} else {
31+
// Note that we're assuming N.N.N here. If we see anything else we are likely to
32+
// mis-parse it.
33+
values[vi] = value
34+
vi++
35+
if vi >= len(values) {
36+
break
37+
}
38+
}
39+
}
40+
switch vi {
41+
case 0:
42+
return 0, 0
43+
case 1:
44+
return values[0], 0
45+
case 2:
46+
return values[0], values[1]
47+
}
48+
return
49+
}
50+
1751
// CopyFileRange copies at most remain bytes of data from src to dst, using
1852
// the copy_file_range system call. dst and src must refer to regular files.
1953
func CopyFileRange(dst, src *FD, remain int64) (written int64, handled bool, err error) {
20-
if atomic.LoadInt32(&copyFileRangeSupported) == 0 {
54+
if supported := atomic.LoadInt32(&copyFileRangeSupported); supported == 0 {
2155
return 0, false, nil
56+
} else if supported == -1 {
57+
major, minor := kernelVersion()
58+
if major > 5 || (major == 5 && minor >= 3) {
59+
atomic.StoreInt32(&copyFileRangeSupported, 1)
60+
} else {
61+
// copy_file_range(2) is broken in various ways on kernels older than 5.3,
62+
// see issue #42400 and
63+
// https://man7.org/linux/man-pages/man2/copy_file_range.2.html#VERSIONS
64+
atomic.StoreInt32(&copyFileRangeSupported, 0)
65+
return 0, false, nil
66+
}
2267
}
2368
for remain > 0 {
2469
max := remain

0 commit comments

Comments
 (0)