mirror of https://github.com/golang/go.git
internal/poll: if copy_file_range returns 0, assume it failed
On current Linux kernels copy_file_range does not correctly handle files in certain special file systems, such as /proc. For those file systems it fails to copy any data and returns zero. This breaks Go's io.Copy for those files. Fix the problem by assuming that if copy_file_range returns 0 the first time it is called on a file, that that file is not supported. In that case fall back to just using read. This will force an extra system call when using io.Copy to copy a zero-sized normal file, but at least it will work correctly. For #36817 Fixes #44272 Change-Id: I02e81872cb70fda0ce5485e2ea712f219132e614 Reviewed-on: https://go-review.googlesource.com/c/go/+/291989 Trust: Ian Lance Taylor <iant@golang.org> Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Russ Cox <rsc@golang.org>
This commit is contained in:
parent
33d72fd412
commit
30641e36aa
|
|
@ -112,7 +112,15 @@ func CopyFileRange(dst, src *FD, remain int64) (written int64, handled bool, err
|
||||||
return 0, false, nil
|
return 0, false, nil
|
||||||
case nil:
|
case nil:
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
// src is at EOF, which means we are done.
|
// If we did not read any bytes at all,
|
||||||
|
// then this file may be in a file system
|
||||||
|
// where copy_file_range silently fails.
|
||||||
|
// https://lore.kernel.org/linux-fsdevel/20210126233840.GG4626@dread.disaster.area/T/#m05753578c7f7882f6e9ffe01f981bc223edef2b0
|
||||||
|
if written == 0 {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
// Otherwise src is at EOF, which means
|
||||||
|
// we are done.
|
||||||
return written, true, nil
|
return written, true, nil
|
||||||
}
|
}
|
||||||
remain -= n
|
remain -= n
|
||||||
|
|
|
||||||
|
|
@ -361,3 +361,35 @@ func (h *copyFileRangeHook) install() {
|
||||||
func (h *copyFileRangeHook) uninstall() {
|
func (h *copyFileRangeHook) uninstall() {
|
||||||
*PollCopyFileRangeP = h.original
|
*PollCopyFileRangeP = h.original
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On some kernels copy_file_range fails on files in /proc.
|
||||||
|
func TestProcCopy(t *testing.T) {
|
||||||
|
const cmdlineFile = "/proc/self/cmdline"
|
||||||
|
cmdline, err := os.ReadFile(cmdlineFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Skipf("can't read /proc file: %v", err)
|
||||||
|
}
|
||||||
|
in, err := os.Open(cmdlineFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
outFile := filepath.Join(t.TempDir(), "cmdline")
|
||||||
|
out, err := os.Create(outFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(out, in); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := out.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
copy, err := os.ReadFile(outFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(cmdline, copy) {
|
||||||
|
t.Errorf("copy of %q got %q want %q\n", cmdlineFile, copy, cmdline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue