| // Copyright 2017 syzkaller project authors. All rights reserved. |
| // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. |
| |
| // Package vmimpl provides an abstract test machine (VM, physical machine, etc) |
| // interface for the rest of the system. For convenience test machines are subsequently |
| // collectively called VMs. |
| // The package also provides various utility functions for VM implementations. |
| package vmimpl |
| |
| import ( |
| "errors" |
| "fmt" |
| "io" |
| "math/rand" |
| "net" |
| "os/exec" |
| "time" |
| |
| "github.com/google/syzkaller/pkg/log" |
| ) |
| |
| // Pool represents a set of test machines (VMs, physical devices, etc) of particular type. |
| type Pool interface { |
| // Count returns total number of VMs in the pool. |
| Count() int |
| |
| // Create creates and boots a new VM instance. |
| Create(workdir string, index int) (Instance, error) |
| } |
| |
| // Instance represents a single VM. |
| type Instance interface { |
| // Copy copies a hostSrc file into VM and returns file name in VM. |
| Copy(hostSrc string) (string, error) |
| |
| // Forward setups forwarding from within VM to host port port |
| // and returns address to use in VM. |
| Forward(port int) (string, error) |
| |
| // Run runs cmd inside of the VM (think of ssh cmd). |
| // outc receives combined cmd and kernel console output. |
| // errc receives either command Wait return error or vmimpl.ErrTimeout. |
| // Command is terminated after timeout. Send on the stop chan can be used to terminate it earlier. |
| Run(timeout time.Duration, stop <-chan bool, command string) (outc <-chan []byte, errc <-chan error, err error) |
| |
| // Diagnose forces VM to dump additional debugging info |
| // (e.g. sending some sys-rq's or SIGABORT'ing a Go program). |
| // Returns true if it did anything. |
| Diagnose() bool |
| |
| // Close stops and destroys the VM. |
| Close() |
| } |
| |
| // Env contains global constant parameters for a pool of VMs. |
| type Env struct { |
| // Unique name |
| // Can be used for VM name collision resolution if several pools share global name space. |
| Name string |
| OS string // target OS |
| Arch string // target arch |
| Workdir string |
| Image string |
| SSHKey string |
| SSHUser string |
| Debug bool |
| Config []byte // json-serialized VM-type-specific config |
| } |
| |
| // BootError is returned by Pool.Create when VM does not boot. |
| type BootError struct { |
| Title string |
| Output []byte |
| } |
| |
| func (err BootError) Error() string { |
| return fmt.Sprintf("%v\n%s", err.Title, err.Output) |
| } |
| |
| func (err BootError) BootError() (string, []byte) { |
| return err.Title, err.Output |
| } |
| |
| // Create creates a VM type that can be used to create individual VMs. |
| func Create(typ string, env *Env) (Pool, error) { |
| ctor := ctors[typ] |
| if ctor == nil { |
| return nil, fmt.Errorf("unknown instance type '%v'", typ) |
| } |
| return ctor(env) |
| } |
| |
| // Register registers a new VM type within the package. |
| func Register(typ string, ctor ctorFunc) { |
| ctors[typ] = ctor |
| } |
| |
| var ( |
| // Close to interrupt all pending operations in all VMs. |
| Shutdown = make(chan struct{}) |
| ErrTimeout = errors.New("timeout") |
| |
| ctors = make(map[string]ctorFunc) |
| ) |
| |
| type ctorFunc func(env *Env) (Pool, error) |
| |
| func Multiplex(cmd *exec.Cmd, merger *OutputMerger, console io.Closer, timeout time.Duration, |
| stop, closed <-chan bool, debug bool) (<-chan []byte, <-chan error, error) { |
| errc := make(chan error, 1) |
| signal := func(err error) { |
| select { |
| case errc <- err: |
| default: |
| } |
| } |
| go func() { |
| select { |
| case <-time.After(timeout): |
| signal(ErrTimeout) |
| case <-stop: |
| signal(ErrTimeout) |
| case <-closed: |
| if debug { |
| log.Logf(0, "instance closed") |
| } |
| signal(fmt.Errorf("instance closed")) |
| case err := <-merger.Err: |
| cmd.Process.Kill() |
| console.Close() |
| merger.Wait() |
| if cmdErr := cmd.Wait(); cmdErr == nil { |
| // If the command exited successfully, we got EOF error from merger. |
| // But in this case no error has happened and the EOF is expected. |
| err = nil |
| } |
| signal(err) |
| return |
| } |
| cmd.Process.Kill() |
| console.Close() |
| merger.Wait() |
| cmd.Wait() |
| }() |
| return merger.Output, errc, nil |
| } |
| |
| func RandomPort() int { |
| return rand.Intn(64<<10-1<<10) + 1<<10 |
| } |
| |
| func UnusedTCPPort() int { |
| for { |
| port := RandomPort() |
| ln, err := net.Listen("tcp", fmt.Sprintf("localhost:%v", port)) |
| if err == nil { |
| ln.Close() |
| return port |
| } |
| } |
| } |