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