blob: 9af669e87d4a38d8cf6670a82772fc3855c99fab [file] [log] [blame]
// 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 fuzz
import (
"fmt"
"io"
"time"
"github.com/golang/glog"
)
// An Instance is a specific combination of build, connector, and launcher,
// representable by a handle. Most methods of this interface map directly to
// the ClusterFuchsia API.
type Instance interface {
Start() error
Stop() error
ListFuzzers() []string
Get(fuzzerName, targetSrc, hostDst string) error
Put(fuzzerName, hostSrc, targetDst string) error
RunFuzzer(out io.Writer, name, hostArtifactDir string, args ...string) error
Handle() (Handle, error)
Close()
}
// BaseInstance groups the core subobjects, to which most work will be delegated
type BaseInstance struct {
Build Build
Connector Connector
Launcher Launcher
}
// NewInstance creates a fresh instance
func NewInstance() (Instance, error) {
build, err := NewBuild()
if err != nil {
return nil, fmt.Errorf("Error configuring build: %s", err)
}
if err := build.Prepare(); err != nil {
return nil, fmt.Errorf("Error preparing build: %s", err)
}
// TODO(fxbug.dev/47479): should the user be able to choose connector/launcher types?
launcher := NewQemuLauncher(build)
// Note: We can't get a Connector until the Launcher has started
return &BaseInstance{build, nil, launcher}, nil
}
func loadInstanceFromHandle(handle Handle) (Instance, error) {
// TODO(fxbug.dev/47320): Store build info in the handle too
build, err := NewBuild()
if err != nil {
return nil, fmt.Errorf("Error configuring build: %s", err)
}
connector, err := loadConnectorFromHandle(handle)
if err != nil {
return nil, err
}
launcher, err := loadLauncherFromHandle(build, handle)
if err != nil {
return nil, err
}
return &BaseInstance{build, connector, launcher}, nil
}
// Close releases the Instance, but doesn't Stop it
func (i *BaseInstance) Close() {
i.Connector.Close()
}
// Start boots up the instance and waits for connectivity to be established
func (i *BaseInstance) Start() error {
conn, err := i.Launcher.Start()
if err != nil {
return err
}
i.Connector = conn
glog.Infof("Waiting for connectivity...")
sleep := 3 * time.Second
success := false
for i := 0; i < 3; i++ {
cmd := conn.Command("echo", "hello")
cmd.SetTimeout(5 * time.Second)
if err := cmd.Start(); err != nil {
return err
}
if err := cmd.Wait(); err != nil {
// If you forgot to --with-base the devtools:
// error: 2 (/boot/bin/sh: 1: Cannot create child process: -1 (ZX_ERR_INTERNAL):
// failed to resolve fuchsia-pkg://fuchsia.com/ls#bin/ls
glog.Warningf("Got error during attempt %d: %s", i, err)
glog.Warningf("Retrying in %s...", sleep)
time.Sleep(sleep)
} else {
glog.Info("Instance is now online.")
success = true
if sc, ok := conn.(*SSHConnector); ok {
glog.Infof("Access via: ssh -i'%s' %s:%d", sc.Key, sc.Host, sc.Port)
}
break
}
}
if !success {
return fmt.Errorf("error establishing connectivity to instance")
}
return nil
}
// RunFuzzer runs the named fuzzer on the Instance. If `hostArtifactDir` is
// specified and the run generated any output artifacts, they will be copied to
// that directory. `args` is an optional list of arguments in the form
// `-key=value` that will be passed to libFuzzer.
func (i *BaseInstance) RunFuzzer(out io.Writer, name, hostArtifactDir string, args ...string) error {
fuzzer, err := i.Build.Fuzzer(name)
if err != nil {
return err
}
fuzzer.Parse(args)
artifacts, err := fuzzer.Run(i.Connector, out, hostArtifactDir)
if err != nil {
return err
}
if hostArtifactDir != "" {
for _, art := range artifacts {
if err := i.Get(name, art, hostArtifactDir); err != nil {
return err
}
}
}
return nil
}
// Get copies files from a fuzzer namespace on the Instance to the host
func (i *BaseInstance) Get(fuzzerName, targetSrc, hostDst string) error {
fuzzer, err := i.Build.Fuzzer(fuzzerName)
if err != nil {
return err
}
return i.Connector.Get(fuzzer.AbsPath(targetSrc), hostDst)
}
// Put copies files from the host to a fuzzer namespace on the Instance
func (i *BaseInstance) Put(fuzzerName, hostSrc, targetDst string) error {
fuzzer, err := i.Build.Fuzzer(fuzzerName)
if err != nil {
return err
}
return i.Connector.Put(hostSrc, fuzzer.AbsPath(targetDst))
}
// Stop shuts down the Instance
func (i *BaseInstance) Stop() error {
return i.Launcher.Kill()
}
// Handle returns a Handle representing the Instance
func (i *BaseInstance) Handle() (Handle, error) {
handle, err := NewHandleFromObjects(i.Connector, i.Launcher)
if err != nil {
return nil, fmt.Errorf("error constructing instance handle: %s", err)
}
return handle, nil
}
// ListFuzzers lists fuzzers available on the Instance
func (i *BaseInstance) ListFuzzers() []string {
return i.Build.ListFuzzers()
}