Skip to content

Commit 30641e3

Browse files
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 <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Russ Cox <[email protected]>
1 parent 33d72fd commit 30641e3

File tree

2 files changed

+41
-1
lines changed

2 files changed

+41
-1
lines changed

src/internal/poll/copy_file_range_linux.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,15 @@ func CopyFileRange(dst, src *FD, remain int64) (written int64, handled bool, err
112112
return 0, false, nil
113113
case nil:
114114
if n == 0 {
115-
// src is at EOF, which means we are done.
115+
// If we did not read any bytes at all,
116+
// then this file may be in a file system
117+
// where copy_file_range silently fails.
118+
// https://lore.kernel.org/linux-fsdevel/[email protected]/T/#m05753578c7f7882f6e9ffe01f981bc223edef2b0
119+
if written == 0 {
120+
return 0, false, nil
121+
}
122+
// Otherwise src is at EOF, which means
123+
// we are done.
116124
return written, true, nil
117125
}
118126
remain -= n

src/os/readfrom_linux_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -361,3 +361,35 @@ func (h *copyFileRangeHook) install() {
361361
func (h *copyFileRangeHook) uninstall() {
362362
*PollCopyFileRangeP = h.original
363363
}
364+
365+
// On some kernels copy_file_range fails on files in /proc.
366+
func TestProcCopy(t *testing.T) {
367+
const cmdlineFile = "/proc/self/cmdline"
368+
cmdline, err := os.ReadFile(cmdlineFile)
369+
if err != nil {
370+
t.Skipf("can't read /proc file: %v", err)
371+
}
372+
in, err := os.Open(cmdlineFile)
373+
if err != nil {
374+
t.Fatal(err)
375+
}
376+
defer in.Close()
377+
outFile := filepath.Join(t.TempDir(), "cmdline")
378+
out, err := os.Create(outFile)
379+
if err != nil {
380+
t.Fatal(err)
381+
}
382+
if _, err := io.Copy(out, in); err != nil {
383+
t.Fatal(err)
384+
}
385+
if err := out.Close(); err != nil {
386+
t.Fatal(err)
387+
}
388+
copy, err := os.ReadFile(outFile)
389+
if err != nil {
390+
t.Fatal(err)
391+
}
392+
if !bytes.Equal(cmdline, copy) {
393+
t.Errorf("copy of %q got %q want %q\n", cmdlineFile, copy, cmdline)
394+
}
395+
}

0 commit comments

Comments
 (0)