Home | History | Annotate | Download | only in vmimpl
      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.
      8 package vmimpl
      9 
     10 import (
     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.
     23 type 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.
     32 type 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.
     56 type 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.
     71 type BootError struct {
     72 	Title  string
     73 	Output []byte
     74 }
     75 
     76 func (err BootError) Error() string {
     77 	return fmt.Sprintf("%v\n%s", err.Title, err.Output)
     78 }
     79 
     80 func (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.
     85 func 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.
     94 func Register(typ string, ctor ctorFunc) {
     95 	ctors[typ] = ctor
     96 }
     97 
     98 var (
     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 
    106 type ctorFunc func(env *Env) (Pool, error)
    107 
    108 func 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 
    148 func RandomPort() int {
    149 	return rand.Intn(64<<10-1<<10) + 1<<10
    150 }
    151 
    152 func 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