blob: 4a38f44eef315d2412557fcd81344ea88bc8c615 [file] [edit]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package virtual_device
import (
"errors"
"fmt"
"math"
"regexp"
"strconv"
"strings"
"go.fuchsia.dev/fuchsia/tools/lib/productbundle"
fvdpb "go.fuchsia.dev/fuchsia/tools/virtual_device/proto"
)
// Regular expressions for validating FVD properties.
var (
// ramRe matches a system RAM size description, like '50G'.
//
// FindStringSubmatch captures three groups from a valid string: The first is the
// entire string, the second and third are the ram number and units respectively.
//
// See //tools/virtual_device/proto/virtual_device.proto for a full description of the
// format.
ramRe = regexp.MustCompile(`^([0-9]+)([mMgG])$`)
// macRe matches a MAC address.
macRe = regexp.MustCompile(`^([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$`)
)
// Default returns a VirtualDevice with default values.
func Default() *fvdpb.VirtualDevice {
return &fvdpb.VirtualDevice{
Name: "default",
Kernel: "qemu-kernel",
Initrd: "zircon-a",
Drive: &fvdpb.Drive{
Id: "maindisk",
Image: "storage-full",
},
Hw: &fvdpb.HardwareProfile{
Arch: "x64",
CpuCount: 1,
Ram: "1M",
Mac: "52:54:00:63:5e:7a",
},
}
}
// ImageKey uniquely identifies a system image by its name and type.
type ImageKey struct {
Name string
Type string
}
// ImageOverrides is a map of image keys to their overridden absolute host paths.
// This is used to bypass the paths defined in the product bundle.
type ImageOverrides map[ImageKey]string
// Validate returns nil iff the given FVD is valid for the given product bundle and overrides.
//
// All system images referenced in the FVD must exist in the product bundle or overrides.
func Validate(fvd *fvdpb.VirtualDevice, pb *productbundle.ProductBundle, overrides ImageOverrides) error {
if fvd == nil {
return errors.New("virtual device cannot be nil")
}
if pb == nil {
return errors.New("product bundle cannot be nil")
}
// Ensure the images referenced in the FVD exist in the image manifest or overrides.
imageByNameAndType := map[ImageKey][]string{}
for _, image := range pb.SystemA {
key := ImageKey{Name: image.Name, Type: image.Type}
imageByNameAndType[key] = append(imageByNameAndType[key], pb.ImagePath(image))
}
for _, image := range pb.SystemR {
if image.Type == "zbi" || image.Type == "vbmeta" {
key := ImageKey{Name: "zircon-r", Type: image.Type}
imageByNameAndType[key] = append(imageByNameAndType[key], pb.ImagePath(image))
}
}
// Apply overrides.
for key, path := range overrides {
imageByNameAndType[key] = []string{path}
}
// A helper function that ensures an image exists in the manifest, has a unique
// name within the manifest, and has a non-empty path.
uniqueImageExists := func(name, typ string) error {
if imagePaths, ok := imageByNameAndType[ImageKey{Name: name, Type: typ}]; !ok {
return fmt.Errorf("image %q of type %q not found", name, typ)
} else if len(imagePaths) != 1 {
return fmt.Errorf("manifest contains multiple images named %q of type %q: %v", name, typ, imagePaths)
} else if imagePaths[0] == "" {
return fmt.Errorf("no path specified for image %q", name)
}
return nil
}
if err := uniqueImageExists(fvd.Kernel, "kernel"); err != nil {
return err
}
if err := uniqueImageExists(fvd.Initrd, "zbi"); err != nil {
return err
}
// If drive points to a file instead of an entry in the image manifest, the filepath
// will be checked at runtime instead since it may not exist when this function is
// called (e.g. it could be a MinFS image which is created during a test run).
if fvd.Drive != nil && !fvd.Drive.IsFilename {
if err := uniqueImageExists(fvd.Drive.Image, "blk"); err != nil {
return fmt.Errorf("%s", err)
}
}
if !isValidRAM(fvd.Hw.Ram) {
return fmt.Errorf("invalid ram: %q", fvd.Hw.Ram)
}
if !isValidArch(fvd.Hw.Arch) {
return fmt.Errorf("invalid arch: %q", fvd.Hw.Arch)
}
if !isValidMAC(fvd.Hw.Mac) {
return fmt.Errorf("invalid MAC address: %q", fvd.Hw.Mac)
}
return nil
}
// ResolveImage resolves the path to the image of the given name and type,
// checking overrides first, then the product bundle.
func ResolveImage(pb *productbundle.ProductBundle, overrides ImageOverrides, name, typ string) (string, error) {
if path, ok := overrides[ImageKey{Name: name, Type: typ}]; ok {
return path, nil
}
if name == "zircon-r" {
for _, image := range pb.SystemR {
if image.Type == typ {
return pb.ImagePath(image), nil
}
}
}
for _, image := range pb.SystemA {
if image.Name == name && image.Type == typ {
return pb.ImagePath(image), nil
}
}
return "", fmt.Errorf("could not find %s of type %s", name, typ)
}
func isValidRAM(ram string) bool {
return ramRe.MatchString(ram)
}
func isValidArch(arch string) bool {
return arch == "x64" || arch == "arm64"
}
func isValidMAC(mac string) bool {
return macRe.MatchString(mac)
}
func parseRAMBytes(ram string) (int, error) {
if !isValidRAM(ram) {
return -1, fmt.Errorf("invalid ram: %q", ram)
}
matches := ramRe.FindStringSubmatch(ram)
size, err := strconv.ParseInt(matches[1], 10, 64)
if err != nil {
return -1, err
}
unit := strings.ToLower(matches[2])
power := map[string]float64{"m": 0, "g": 1}[unit]
bytes := int(size * int64(math.Pow(1024, power)))
return bytes, nil
}