a73x

internal/agent/imagecache/format.go

Ref:   Size: 1.7 KiB   History

package imagecache

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
)

// imageFormat is what a download's first bytes say it is. Detection is by magic
// number rather than URL suffix because the suffix lies: the Ubuntu cloud
// images the fleet boots by default are named ".img" and are qcow2 inside.
type imageFormat string

const (
	formatRaw   imageFormat = "raw"
	formatGzip  imageFormat = "gzip"
	formatQcow2 imageFormat = "qcow2"
)

var (
	gzipMagic  = []byte{0x1f, 0x8b}
	qcow2Magic = []byte{'Q', 'F', 'I', 0xfb}
)

// magicLen is the longest magic number above: a download shorter than this
// cannot be anything but raw.
const magicLen = 4

// sniff classifies a download from its leading bytes. Anything unrecognized is
// raw — raw has no magic number of its own, and an image the agent cannot name
// is one the guest firmware gets to interpret rather than one the agent
// rejects.
func sniff(head []byte) imageFormat {
	switch {
	case bytes.HasPrefix(head, gzipMagic):
		return formatGzip
	case bytes.HasPrefix(head, qcow2Magic):
		return formatQcow2
	default:
		return formatRaw
	}
}

// sniffFile classifies the image file at path. A file shorter than magicLen is
// read in full and classified on what it has, so a truncated or empty image is
// raw rather than an error — the checksum has already vouched for the bytes by
// the time anything calls this.
func sniffFile(path string) (imageFormat, error) {
	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()

	head := make([]byte, magicLen)
	n, err := io.ReadFull(f, head)
	if err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) {
		return "", fmt.Errorf("read image head: %w", err)
	}
	return sniff(head[:n]), nil
}