[recovery] Add scheme_map config
appmgr no longer boots without a scheme_map, which means recovery was
crashing on startup prior to this CL.
Testing this change requires building out a testing framework for
running QEMU directly because we need to run the recovery system rather
than a normal Fuchsia system.
Change-Id: If640a0886e3d690c90661e6e62879ce997ee7857
diff --git a/build/config/fuchsia/zbi.gni b/build/config/fuchsia/zbi.gni
index 0facf2a..77920e80 100644
--- a/build/config/fuchsia/zbi.gni
+++ b/build/config/fuchsia/zbi.gni
@@ -77,8 +77,9 @@
forward_variables_from(invoker,
[
"deps",
- "visibility",
+ "metadata",
"testonly",
+ "visibility",
])
outputs = [
output_file,
diff --git a/build/images/assemble_system.gni b/build/images/assemble_system.gni
index c372f1e..e1d5b51 100644
--- a/build/images/assemble_system.gni
+++ b/build/images/assemble_system.gni
@@ -239,6 +239,20 @@
}
zbi(image_name) {
+ metadata = {
+ # We insert a package barrier because the packages inside this ZBI
+ # shouldn't leak to targets that depend on the ZBI. For example,
+ # suppose we store this ZBI inside a package() that is assembled into
+ # another Fuchsia system. We don't want the parent system to incorporate
+ # the packages from the ZBI into its own package list.
+ package_barrier = []
+ config_package_barrier = []
+
+ if (defined(invoker.metadata)) {
+ forward_variables_from(invoker.metadata, [ "metadata" ])
+ }
+ }
+
testonly = true
deps = [
":devmgr_config.txt",
diff --git a/build/images/recovery/BUILD.gn b/build/images/recovery/BUILD.gn
index 34f1eaa..b4bfef7 100644
--- a/build/images/recovery/BUILD.gn
+++ b/build/images/recovery/BUILD.gn
@@ -8,6 +8,7 @@
recovery_packages = [
"//garnet:build-info",
+ "//garnet/bin/appmgr:appmgr_scheme_config",
"//garnet/bin/appmgr",
"//garnet/bin/netcfg:config",
"//garnet/bin/netcfg",
@@ -46,4 +47,8 @@
base_packages = recovery_packages
netboot = true
devmgr_config = [ "virtcon.disable=true" ]
+
+ metadata = {
+ test_runtime_deps = [ "$root_out_dir/${target_name}.zbi" ]
+ }
}
diff --git a/src/recovery/simulator/BUILD.gn b/src/recovery/simulator/BUILD.gn
index dde3f8f..4ebfd4c 100644
--- a/src/recovery/simulator/BUILD.gn
+++ b/src/recovery/simulator/BUILD.gn
@@ -29,5 +29,6 @@
deps = [
":lib",
]
+ non_go_deps = [ "//build/images/recovery($target_toolchain)" ]
}
}
diff --git a/src/recovery/simulator/simulator_test.go b/src/recovery/simulator/simulator_test.go
index 12f7797..cf1626b 100644
--- a/src/recovery/simulator/simulator_test.go
+++ b/src/recovery/simulator/simulator_test.go
@@ -5,15 +5,46 @@
package simulator
import (
+ "os"
+ "path/filepath"
"testing"
"fuchsia.googlesource.com/testing/qemu"
)
+func zbiPath(t *testing.T) string {
+ ex, err := os.Executable()
+ if err != nil {
+ t.Fatal(err)
+ return ""
+ }
+ exPath := filepath.Dir(ex)
+ return filepath.Join(exPath, "../recovery.zbi")
+}
+
// TestUnpack checks that we can unpack qemu.
-func TestUnpack(t *testing.T) {
- err := qemu.Unpack()
+func TestBoot(t *testing.T) {
+ distro, err := qemu.Unpack()
if err != nil {
t.Fatal(err)
}
+ defer distro.Delete()
+
+ arch, err := distro.TargetCPU()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ i := distro.Create(qemu.Params{
+ Arch: arch,
+ ZBI: zbiPath(t),
+ })
+
+ i.Start()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer i.Kill()
+
+ i.WaitForLogMessage("recovery: started")
}
diff --git a/src/testing/qemu/BUILD.gn b/src/testing/qemu/BUILD.gn
index 01aa5f4..6f90b6e 100644
--- a/src/testing/qemu/BUILD.gn
+++ b/src/testing/qemu/BUILD.gn
@@ -3,6 +3,7 @@
# found in the LICENSE file.
import("//build/go/go_library.gni")
+import("//build/config/fuchsia/zircon.gni")
# Currently, we ony support working with the QEMU simulator on Linux.
if (is_linux) {
@@ -36,8 +37,34 @@
}
}
+ copy("kernel") {
+ visibility = [ ":*" ]
+
+ kernel_binary = ""
+ if (target_cpu == "arm64") {
+ kernel_binary = "qemu-boot-shim.bin"
+ } else if (target_cpu == "x64") {
+ kernel_binary = "multiboot.bin"
+ } else {
+ assert(false, "CPU not supported")
+ }
+ target_cpu_txt = "$root_out_dir/test_data/qemu/target_cpu.txt"
+
+ sources = [ "$zircon_root_build_dir/$kernel_binary" ]
+ outputs = [ "$root_out_dir/test_data/qemu/$kernel_binary" ]
+
+ write_file(target_cpu_txt, target_cpu)
+
+ metadata = {
+ test_runtime_deps = [
+ outputs[0],
+ target_cpu_txt,
+ ]
+ }
+ }
+
go_library("qemu") {
name = "fuchsia.googlesource.com/testing/qemu"
- non_go_deps = [ ":archive" ]
+ non_go_deps = [ ":archive", ":kernel" ]
}
}
diff --git a/src/testing/qemu/qemu.go b/src/testing/qemu/qemu.go
index 43abb0d..5697280 100644
--- a/src/testing/qemu/qemu.go
+++ b/src/testing/qemu/qemu.go
@@ -5,19 +5,215 @@
package qemu
import (
+ "archive/tar"
+ "bufio"
+ "compress/gzip"
+ "fmt"
+ "io"
+ "io/ioutil"
"os"
+ "os/exec"
"path/filepath"
+ "strings"
)
-// Unpack the QEMU instance.
-func Unpack() error {
- ex, err := os.Executable()
+// Untar untars a tar.gz file into a directory.
+func untar(dst string, src string) error {
+ f, err := os.Open(src)
if err != nil {
return err
}
+ defer f.Close()
+
+ gz, err := gzip.NewReader(f)
+ if err != nil {
+ return err
+ }
+ defer gz.Close()
+
+ tr := tar.NewReader(gz)
+
+ for {
+ header, err := tr.Next()
+ if err == io.EOF {
+ return nil
+ } else if err != nil {
+ return err
+ }
+
+ path := filepath.Join(dst, header.Name)
+ info := header.FileInfo()
+ if info.IsDir() {
+ if err := os.MkdirAll(path, info.Mode()); err != nil {
+ return err
+ }
+ } else {
+ if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
+ return err
+ }
+
+ f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, info.Mode())
+ if err != nil {
+ return err
+ }
+
+ if _, err := io.Copy(f, tr); err != nil {
+ f.Close()
+ return err
+ }
+
+ f.Close()
+ }
+ }
+}
+
+// Distribution is a collection of QEMU-related artifacts.
+type Distribution struct {
+ exPath string
+ unpackedPath string
+}
+
+type Arch int
+
+const (
+ X64 Arch = iota
+ Arm64
+)
+
+// Params describes how to run a QEMU instance.
+type Params struct {
+ Arch Arch
+ ZBI string
+}
+
+type Instance struct {
+ cmd *exec.Cmd
+ stdin *bufio.Writer
+ stdout *bufio.Reader
+ stderr *bufio.Reader
+}
+
+// Unpack unpacks the QEMU distribution.
+func Unpack() (*Distribution, error) {
+ ex, err := os.Executable()
+ if err != nil {
+ return nil, err
+ }
exPath := filepath.Dir(ex)
archivePath := filepath.Join(exPath, "test_data/qemu/qemu.tar.gz")
- _, err = os.Stat(archivePath)
+ unpackedPath, err := ioutil.TempDir("", "qemu-distro")
+ if err != nil {
+ return nil, err
+ }
+
+ err = untar(unpackedPath, archivePath)
+ if err != nil {
+ os.RemoveAll(unpackedPath)
+ return nil, err
+ }
+
+ return &Distribution{exPath: exPath, unpackedPath: unpackedPath}, nil
+}
+
+// Delete removes the QEMU-related artifacts.
+func (d *Distribution) Delete() {
+ os.RemoveAll(d.unpackedPath)
+}
+
+func (d *Distribution) systemPath(arch Arch) string {
+ switch arch {
+ case X64:
+ return filepath.Join(d.unpackedPath, "bin/qemu-system-x86_64")
+ case Arm64:
+ return filepath.Join(d.unpackedPath, "bin/qemu-system-aarch64")
+ }
+ return ""
+}
+
+func (d *Distribution) kernelPath(arch Arch) string {
+ switch arch {
+ case X64:
+ return filepath.Join(d.exPath, "test_data/qemu/multiboot.bin")
+ case Arm64:
+ return filepath.Join(d.exPath, "test_data/qemu/qemu-boot-shim.bin")
+ }
+ return ""
+}
+
+// TargetCPU returs the target CPU used by the build that produced this library.
+func (d *Distribution) TargetCPU() (Arch, error) {
+ path := filepath.Join(d.exPath, "test_data/qemu/target_cpu.txt")
+ bytes, err := ioutil.ReadFile(path)
+ if err != nil {
+ return X64, err
+ }
+ name := string(bytes)
+ switch name {
+ case "x64":
+ return X64, nil
+ case "arm64":
+ return Arm64, nil
+ }
+ return X64, fmt.Errorf("unknown target CPU: %s", name)
+}
+
+// Create creates an instance of QEMU with the given parameters.
+func (d *Distribution) Create(params Params) *Instance {
+ path := d.systemPath(params.Arch)
+ args := []string{}
+ args = append(args, "-kernel", d.kernelPath(params.Arch), "-initrd", params.ZBI)
+ args = append(args, "-m", "2048", "-nographic", "-net", "none", "-smp", "4,threads=2",
+ "-machine", "q35", "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04",
+ "-cpu", "Haswell,+smap,-check,-fsgsbase")
+ cmdline := "kernel.serial=legacy kernel.entropy-mixin=1420bb81dc0396b37cc2d0aa31bb2785dadaf9473d0780ecee1751afb5867564 kernel.halt-on-panic=true"
+ args = append(args, "-append", cmdline)
+ return &Instance{
+ cmd: exec.Command(path, args...),
+ }
+}
+
+// Start the QEMU instance.
+func (i *Instance) Start() error {
+ stdin, err := i.cmd.StdinPipe()
+ if err != nil {
+ return err
+ }
+ stdout, err := i.cmd.StdoutPipe()
+ if err != nil {
+ return err
+ }
+ stderr, err := i.cmd.StderrPipe()
+ if err != nil {
+ return err
+ }
+ i.stdin = bufio.NewWriter(stdin)
+ i.stdout = bufio.NewReader(stdout)
+ i.stderr = bufio.NewReader(stderr)
+ return i.cmd.Start()
+}
+
+// Kill terminates the QEMU instance.
+func (i *Instance) Kill() error {
+ return i.cmd.Process.Kill()
+}
+
+// RunCommand runs the given command in the serial console for the QEMU instance.
+func (i *Instance) RunCommand(cmd string) error {
+ _, err := i.stdin.WriteString(fmt.Sprintf("%s\n", cmd))
return err
}
+
+// WaitForLogMessage reads log messages from the QEMU instance until it reads
+// a message that contains the given string.
+func (i *Instance) WaitForLogMessage(msg string) error {
+ for {
+ line, err := i.stdout.ReadString('\n')
+ if err != nil {
+ return err
+ }
+ if strings.Contains(line, msg) {
+ return nil
+ }
+ }
+}