1// Copyright 2017 syzkaller project authors. All rights reserved. 2// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4// Package vmimpl provides an abstract test machine (VM, physical machine, etc) 5// interface for the rest of the system. For convenience test machines are subsequently 6// collectively called VMs. 7// The package also provides various utility functions for VM implementations. 8package vmimpl 9 10import ( 11 "errors" 12 "fmt" 13 "io" 14 "math/rand" 15 "net" 16 "os/exec" 17 "time" 18 19 "github.com/google/syzkaller/pkg/log" 20) 21 22// Pool represents a set of test machines (VMs, physical devices, etc) of particular type. 23type Pool interface { 24 // Count returns total number of VMs in the pool. 25 Count() int 26 27 // Create creates and boots a new VM instance. 28 Create(workdir string, index int) (Instance, error) 29} 30 31// Instance represents a single VM. 32type Instance interface { 33 // Copy copies a hostSrc file into VM and returns file name in VM. 34 Copy(hostSrc string) (string, error) 35 36 // Forward setups forwarding from within VM to host port port 37 // and returns address to use in VM. 38 Forward(port int) (string, error) 39 40 // Run runs cmd inside of the VM (think of ssh cmd). 41 // outc receives combined cmd and kernel console output. 42 // errc receives either command Wait return error or vmimpl.ErrTimeout. 43 // Command is terminated after timeout. Send on the stop chan can be used to terminate it earlier. 44 Run(timeout time.Duration, stop <-chan bool, command string) (outc <-chan []byte, errc <-chan error, err error) 45 46 // Diagnose forces VM to dump additional debugging info 47 // (e.g. sending some sys-rq's or SIGABORT'ing a Go program). 48 // Returns true if it did anything. 49 Diagnose() bool 50 51 // Close stops and destroys the VM. 52 Close() 53} 54 55// Env contains global constant parameters for a pool of VMs. 56type Env struct { 57 // Unique name 58 // Can be used for VM name collision resolution if several pools share global name space. 59 Name string 60 OS string // target OS 61 Arch string // target arch 62 Workdir string 63 Image string 64 SSHKey string 65 SSHUser string 66 Debug bool 67 Config []byte // json-serialized VM-type-specific config 68} 69 70// BootError is returned by Pool.Create when VM does not boot. 71type BootError struct { 72 Title string 73 Output []byte 74} 75 76func (err BootError) Error() string { 77 return fmt.Sprintf("%v\n%s", err.Title, err.Output) 78} 79 80func (err BootError) BootError() (string, []byte) { 81 return err.Title, err.Output 82} 83 84// Create creates a VM type that can be used to create individual VMs. 85func Create(typ string, env *Env) (Pool, error) { 86 ctor := ctors[typ] 87 if ctor == nil { 88 return nil, fmt.Errorf("unknown instance type '%v'", typ) 89 } 90 return ctor(env) 91} 92 93// Register registers a new VM type within the package. 94func Register(typ string, ctor ctorFunc) { 95 ctors[typ] = ctor 96} 97 98var ( 99 // Close to interrupt all pending operations in all VMs. 100 Shutdown = make(chan struct{}) 101 ErrTimeout = errors.New("timeout") 102 103 ctors = make(map[string]ctorFunc) 104) 105 106type ctorFunc func(env *Env) (Pool, error) 107 108func Multiplex(cmd *exec.Cmd, merger *OutputMerger, console io.Closer, timeout time.Duration, 109 stop, closed <-chan bool, debug bool) (<-chan []byte, <-chan error, error) { 110 errc := make(chan error, 1) 111 signal := func(err error) { 112 select { 113 case errc <- err: 114 default: 115 } 116 } 117 go func() { 118 select { 119 case <-time.After(timeout): 120 signal(ErrTimeout) 121 case <-stop: 122 signal(ErrTimeout) 123 case <-closed: 124 if debug { 125 log.Logf(0, "instance closed") 126 } 127 signal(fmt.Errorf("instance closed")) 128 case err := <-merger.Err: 129 cmd.Process.Kill() 130 console.Close() 131 merger.Wait() 132 if cmdErr := cmd.Wait(); cmdErr == nil { 133 // If the command exited successfully, we got EOF error from merger. 134 // But in this case no error has happened and the EOF is expected. 135 err = nil 136 } 137 signal(err) 138 return 139 } 140 cmd.Process.Kill() 141 console.Close() 142 merger.Wait() 143 cmd.Wait() 144 }() 145 return merger.Output, errc, nil 146} 147 148func RandomPort() int { 149 return rand.Intn(64<<10-1<<10) + 1<<10 150} 151 152func UnusedTCPPort() int { 153 for { 154 port := RandomPort() 155 ln, err := net.Listen("tcp", fmt.Sprintf("localhost:%v", port)) 156 if err == nil { 157 ln.Close() 158 return port 159 } 160 } 161} 162