diff --git a/libcontainer/dmz/cloned_binary_linux.go b/libcontainer/dmz/cloned_binary_linux.go index 133bdefaf..db5e18a32 100644 --- a/libcontainer/dmz/cloned_binary_linux.go +++ b/libcontainer/dmz/cloned_binary_linux.go @@ -179,7 +179,7 @@ func CloneBinary(src io.Reader, size int64, name, tmpDir string) (*os.File, erro if err != nil { return nil, err } - copied, err := io.Copy(file, src) + copied, err := system.Copy(file, src) if err != nil { file.Close() return nil, fmt.Errorf("copy binary: %w", err) diff --git a/libcontainer/system/linux.go b/libcontainer/system/linux.go index 8f52496e9..318b6edfe 100644 --- a/libcontainer/system/linux.go +++ b/libcontainer/system/linux.go @@ -5,6 +5,7 @@ package system import ( "fmt" + "io" "os" "os/exec" "strconv" @@ -174,3 +175,42 @@ func ExecutableMemfd(comment string, flags int) (*os.File, error) { } return os.NewFile(uintptr(memfd), "/memfd:"+comment), nil } + +// Copy is like io.Copy except it uses sendfile(2) if the source and sink are +// both (*os.File) as an optimisation to make copies faster. +func Copy(dst io.Writer, src io.Reader) (copied int64, err error) { + dstFile, _ := dst.(*os.File) + srcFile, _ := src.(*os.File) + + if dstFile != nil && srcFile != nil { + fi, err := srcFile.Stat() + if err != nil { + goto fallback + } + size := fi.Size() + for size > 0 { + n, err := unix.Sendfile(int(dstFile.Fd()), int(srcFile.Fd()), nil, int(size)) + if n > 0 { + size -= int64(n) + copied += int64(n) + } + if err == unix.EINTR { + continue + } + if err != nil { + if copied == 0 { + // If we haven't copied anything so far, we can safely just + // fallback to io.Copy. We could always do the fallback but + // it's safer to error out in the case of a partial copy + // followed by an error (which should never happen). + goto fallback + } + return copied, fmt.Errorf("partial sendfile copy: %w", err) + } + } + return copied, nil + } + +fallback: + return io.Copy(dst, src) +}