blob: 5697280ec748e334685439f4073092f43c6aee4a [file] [log] [blame]
// Copyright 2019 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 qemu
import (
"archive/tar"
"bufio"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
)
// 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")
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
}
}
}