blob: 29568648aadb1b425ada6700e49bf127af3363de [file] [log] [blame]
// Copyright 2018 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 main
import (
"context"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"fuchsia.googlesource.com/tools/build"
"fuchsia.googlesource.com/tools/qemu"
"fuchsia.googlesource.com/tools/secrets"
"github.com/google/subcommands"
)
// QEMUBinPrefix is the prefix of the QEMU binary name, which is of the form
// qemu-system-<QEMU arch suffix>.
const qemuBinPrefix = "qemu-system"
// QEMUCommand is a Command implementation for running the testing workflow on an emulated
// target within QEMU.
type QEMUCommand struct {
// ImageManifest is the path an image manifest specifying a QEMU kernel, a zircon
// kernel, and block image to back QEMU storage.
imageManifest string
// QEMUBinDir is a path to a directory of QEMU binaries.
qemuBinDir string
// MinFSImage is a path to a minFS image to be mounted on target, and to where test
// results will be written.
minFSImage string
// MinFSBlkDevPCIAddr is a minFS block device PCI address.
minFSBlkDevPCIAddr string
// TargetArch is the target architecture to be emulated within QEMU
targetArch string
// EnableKVM dictates whether to enable KVM.
enableKVM bool
// EnableNetworking dictates whether to enable external networking.
enableNetworking bool
}
func (*QEMUCommand) Name() string {
return "qemu"
}
func (*QEMUCommand) Usage() string {
return "qemu [flags...] [kernel command-line arguments...]\n\nflags:\n"
}
func (*QEMUCommand) Synopsis() string {
return "boots a QEMU device with a MinFS image as a block device."
}
func (cmd *QEMUCommand) SetFlags(f *flag.FlagSet) {
f.StringVar(&cmd.imageManifest, "images", "", "path to an image manifest")
f.StringVar(&cmd.qemuBinDir, "qemu-dir", "", "")
f.StringVar(&cmd.minFSImage, "minfs", "", "path to minFS image")
f.StringVar(&cmd.minFSBlkDevPCIAddr, "pci-addr", "06.0", "minFS block device PCI address")
f.StringVar(&cmd.targetArch, "arch", "", "target architecture (x64 or arm64)")
f.BoolVar(&cmd.enableKVM, "use-kvm", false, "whether to enable KVM")
f.BoolVar(&cmd.enableNetworking, "enable-networking", false, "whether to enable external networking")
}
func (cmd *QEMUCommand) execute(ctx context.Context, cmdlineArgs []string) error {
if cmd.qemuBinDir == "" {
return fmt.Errorf("-qemu-dir must be set")
}
imgs, err := build.LoadImages(cmd.imageManifest)
if err != nil {
return err
}
qemuCPU, ok := map[string]string{
"x64": "x86_64",
"arm64": "aarch64",
}[cmd.targetArch]
if !ok {
return fmt.Errorf("cpu %q not recognized", cmd.targetArch)
}
qemuBinPath := filepath.Join(cmd.qemuBinDir, fmt.Sprintf("%s-%s", qemuBinPrefix, qemuCPU))
cfg := qemu.Config{
QEMUBin: qemuBinPath,
CPU: cmd.targetArch,
KVM: cmd.enableKVM,
MinFSImage: cmd.minFSImage,
PCIAddr: cmd.minFSBlkDevPCIAddr,
InternetAccess: cmd.enableNetworking,
}
// The system will halt on a kernel panic instead of rebooting
cmdlineArgs = append(cmdlineArgs, "kernel.halt-on-panic=true")
// Print a message if `dm poweroff` times out.
cmdlineArgs = append(cmdlineArgs, "devmgr.suspend-timeout-debug=true")
// Do not print colors.
cmdlineArgs = append(cmdlineArgs, "TERM=dumb")
if cmd.targetArch == "x64" {
// Necessary to redirect to stdout.
cmdlineArgs = append(cmdlineArgs, "kernel.serial=legacy")
}
invocation, err := qemu.CreateInvocation(cfg, imgs, cmdlineArgs)
if err != nil {
return err
}
// The QEMU command needs to be invoked within an empty directory, as QEMU will attempt
// to pick up files from its working directory, one notable culprit being multiboot.bin.
// This can result in strange behavior.
qemuWorkingDir, err := ioutil.TempDir("", "qemu-working-dir")
if err != nil {
return err
}
defer os.RemoveAll(qemuWorkingDir)
qemuCmd := exec.Cmd{
Path: invocation[0],
Args: invocation,
Dir: qemuWorkingDir,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
log.Printf("QEMU invocation:\n%s", invocation)
return qemu.CheckExitCode(qemuCmd.Run())
}
func (cmd *QEMUCommand) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
// TODO(IN-607) Once the secrets pipeline is supported on hardware, move the starting
// of the secrets server to botanist's main().
//
// The secrets server will start up iff LUCI_CONTEXT is set and contains secret bytes.
secrets.StartSecretsServer(ctx, 8081)
if err := cmd.execute(ctx, f.Args()); err != nil {
log.Print(err)
return subcommands.ExitFailure
}
return subcommands.ExitSuccess
}