a73x

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)
}