mirror of
https://github.com/kopia/kopia.git
synced 2026-01-24 22:38:00 -05:00
* Unify sparse and normal IO output This commit refactors the code paths that excercise normal and sparse writing of restored content. The goal is to expose sparsefile.Copy() and iocopy.Copy() to be interchangeable, thereby allowing us to wrap or transform their behavior more easily in the future. * Introduce getStreamCopier() * Pull ioCopy() into getStreamCopier() * Fix small nit in E2E test We should be getting the block size of the destination file, not the source file. * Call stat.GetBlockSize() once per FilesystemOutput A tiny refactor to pull this call out of the generated stream copier, as the block size should not change from one file to the next within a restore entry. NOTE: as a side effect, if block size could not be found (an error is returned), we will return the default stream copier instead of letting the sparse copier fail. A warning will be logged, but this error will not cause the restore to fail; it will proceed silently.
79 lines
1.5 KiB
Go
79 lines
1.5 KiB
Go
// Package sparsefile provides wrappers for handling the writing of sparse files (files with holes).
|
|
package sparsefile
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/kopia/kopia/internal/iocopy"
|
|
)
|
|
|
|
// Copy copies a file sparsely (omitting holes) from src to dst, while recycling
|
|
// shared buffers.
|
|
func Copy(dst io.WriteSeeker, src io.Reader, bufSize uint64) (int64, error) {
|
|
buf := iocopy.GetBuffer()
|
|
defer iocopy.ReleaseBuffer(buf)
|
|
|
|
return copyBuffer(dst, src, buf[0:bufSize])
|
|
}
|
|
|
|
// Copy copies bits from src to dst, seeking past blocks of zero bits in src. These
|
|
// blocks are omitted, creating a file with holes in dst.
|
|
func copyBuffer(dst io.WriteSeeker, src io.Reader, buf []byte) (written int64, err error) {
|
|
for {
|
|
nr, er := src.Read(buf)
|
|
if nr > 0 { // nolint:nestif
|
|
// If non-zero data is read, write it. Otherwise, skip forwards.
|
|
if isAllZero(buf) {
|
|
dst.Seek(int64(nr), os.SEEK_CUR) // nolint:errcheck
|
|
written += int64(nr)
|
|
|
|
continue
|
|
}
|
|
|
|
nw, ew := dst.Write(buf[0:nr])
|
|
if nw < 0 || nr < nw {
|
|
nw = 0
|
|
|
|
if ew == nil {
|
|
ew = errors.New("invalid write result")
|
|
}
|
|
}
|
|
|
|
written += int64(nw)
|
|
|
|
if ew != nil {
|
|
err = ew
|
|
break
|
|
}
|
|
|
|
if nr != nw {
|
|
err = io.ErrShortWrite
|
|
break
|
|
}
|
|
}
|
|
|
|
if er != nil {
|
|
if er != io.EOF {
|
|
err = er
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
return written, err
|
|
}
|
|
|
|
func isAllZero(buf []byte) bool {
|
|
for _, b := range buf {
|
|
if b != 0 {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|