internal/agent/imagecache/sparse.go
Ref: Size: 2.1 KiB History
package imagecache
import (
"bytes"
"errors"
"io"
"os"
)
const (
// blockSize is the granularity at which zeros are detected. A hole can only
// start on a filesystem block boundary, so 4 KiB — the block size on ext4,
// xfs, btrfs and APFS — is the finest granularity that buys anything.
blockSize = 4096
// bufSize keeps reads chunky. It is a multiple of blockSize so every buffer
// boundary is also a block boundary, and a zero run crossing two buffers is
// still punched as one hole.
bufSize = 1 << 20
)
// zeroBlock is the comparand for hole detection: bytes.Equal against it is one
// memequal, which is why scanning a multi-gigabyte image block by block costs
// nothing next to the decompression feeding it.
var zeroBlock = make([]byte, blockSize)
// writeSparse copies src into dst, seeking past all-zero blocks instead of
// writing them, so a decompressed cloud image — which is mostly zeros — costs
// only the disk its real data occupies.
//
// dst ends at the stream's exact apparent size. That needs the closing
// Truncate: seeking past the end of a file does not extend it, so a stream
// ending in zeros would otherwise leave a file shorter than the image it
// claims to be, and PrepareRootDisk sizes a guest's disk against that number.
func writeSparse(dst *os.File, src io.Reader) error {
buf := make([]byte, bufSize)
var size int64 // logical bytes consumed, holes included
var hole int64 // consecutive zero bytes not yet seeked past
for {
n, readErr := io.ReadFull(src, buf)
for chunk := buf[:n]; len(chunk) > 0; {
b := chunk
if len(b) > blockSize {
b = b[:blockSize]
}
chunk = chunk[len(b):]
size += int64(len(b))
if bytes.Equal(b, zeroBlock[:len(b)]) {
hole += int64(len(b))
continue
}
if hole > 0 {
if _, err := dst.Seek(hole, io.SeekCurrent); err != nil {
return err
}
hole = 0
}
if _, err := dst.Write(b); err != nil {
return err
}
}
if errors.Is(readErr, io.EOF) || errors.Is(readErr, io.ErrUnexpectedEOF) {
break
}
if readErr != nil {
return readErr
}
}
return dst.Truncate(size)
}