podman/pkg/machine/compression/copy.go

118 lines
2.3 KiB
Go

package compression
import (
"bytes"
"io"
"os"
)
// TODO vendor this in ... pkg/os directory is small and code should be negligible
/*
NOTE: copy.go and copy.test were lifted from github.com/crc-org/crc because
i was having trouble getting go to vendor it properly. all credit to them
*/
func copyFile(src, dst string, sparse bool) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
if sparse {
if _, err = CopySparse(out, in); err != nil {
return err
}
} else {
if _, err = io.Copy(out, in); err != nil {
return err
}
}
fi, err := os.Stat(src)
if err != nil {
return err
}
if err = os.Chmod(dst, fi.Mode()); err != nil {
return err
}
return out.Close()
}
func CopyFile(src, dst string) error {
return copyFile(src, dst, false)
}
func CopyFileSparse(src, dst string) error {
return copyFile(src, dst, true)
}
func CopySparse(dst io.WriteSeeker, src io.Reader) (int64, error) {
copyBuf := make([]byte, copyChunkSize)
sparseWriter := newSparseWriter(dst)
bytesWritten, err := io.CopyBuffer(sparseWriter, src, copyBuf)
if err != nil {
return bytesWritten, err
}
err = sparseWriter.Close()
return bytesWritten, err
}
type sparseWriter struct {
writer io.WriteSeeker
lastChunkSparse bool
}
func newSparseWriter(writer io.WriteSeeker) *sparseWriter {
return &sparseWriter{writer: writer}
}
const copyChunkSize = 4096
var emptyChunk = make([]byte, copyChunkSize)
func isEmptyChunk(p []byte) bool {
// HasPrefix instead of bytes.Equal in order to handle the last chunk
// of the file, which may be shorter than len(emptyChunk), and would
// fail bytes.Equal()
return bytes.HasPrefix(emptyChunk, p)
}
func (w *sparseWriter) Write(p []byte) (n int, err error) {
if isEmptyChunk(p) {
offset, err := w.writer.Seek(int64(len(p)), io.SeekCurrent)
if err != nil {
w.lastChunkSparse = false
return 0, err
}
_ = offset
w.lastChunkSparse = true
return len(p), nil
}
w.lastChunkSparse = false
return w.writer.Write(p)
}
func (w *sparseWriter) Close() error {
if w.lastChunkSparse {
if _, err := w.writer.Seek(-1, io.SeekCurrent); err != nil {
return err
}
if _, err := w.writer.Write([]byte{0}); err != nil {
return err
}
}
return nil
}