diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2017-06-02 20:09:00 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2017-06-03 11:31:42 +0200 |
| commit | af643baa328ae3d4b7076054bba648c4b8bf8056 (patch) | |
| tree | 6e4687c745b63352dec21f6ac2a6a7d8fa1201c4 /vm/vmimpl | |
| parent | 96b8d4e99c7812f91633ea6cd1aee5867965e742 (diff) | |
vm: overhaul
VM infrastructure currently has several problems:
- Config struct is complete mess with a superset of params for all VM types
- verification of Config is mess spread across several places
- there is no place where VM code could do global initialization
like creating GCE connection, uploading GCE image to GCS,
matching adb devices with consoles, etc
- it hard to add private VM implementations
such impl would need to add code to config package
which would lead to constant merge conflicts
- interface for VM implementation is mixed with interface for VM users
this does not allow to provide best interface for both of them
- there is no way to add common code for all VM implementations
This change solves these problems by:
- splitting VM interface for users (vm package) and VM interface
for VM implementations (vmimpl pacakge), this in turn allows
to add common code
- adding Pool concept that allows to do global initialization
and config checking at the right time
- decoupling manager config from VM-specific config
each VM type now defines own config
Note: manager configs need to be changed after this change:
VM-specific parts are moved to own "vm" subobject.
Note: this change also drops "local" VM type.
Its story was long unclear and there is now syz-stress which solves the same problem.
Diffstat (limited to 'vm/vmimpl')
| -rw-r--r-- | vm/vmimpl/console.go | 134 | ||||
| -rw-r--r-- | vm/vmimpl/merger.go | 81 | ||||
| -rw-r--r-- | vm/vmimpl/merger_test.go | 79 | ||||
| -rw-r--r-- | vm/vmimpl/util.go | 19 | ||||
| -rw-r--r-- | vm/vmimpl/vmimpl.go | 77 |
5 files changed, 390 insertions, 0 deletions
diff --git a/vm/vmimpl/console.go b/vm/vmimpl/console.go new file mode 100644 index 000000000..f4a0b71d1 --- /dev/null +++ b/vm/vmimpl/console.go @@ -0,0 +1,134 @@ +// 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. + +// +build !ppc64le + +package vmimpl + +import ( + "fmt" + "io" + "os/exec" + "sync" + "syscall" + "unsafe" + + "github.com/google/syzkaller/pkg/osutil" + "golang.org/x/sys/unix" +) + +// Tested on Suzy-Q and BeagleBone. +func OpenConsole(con string) (rc io.ReadCloser, err error) { + fd, err := syscall.Open(con, syscall.O_RDONLY|syscall.O_NOCTTY|syscall.O_SYNC, 0) + if err != nil { + return nil, fmt.Errorf("failed to open console file: %v", err) + } + defer func() { + if fd != -1 { + syscall.Close(fd) + } + }() + var term unix.Termios + if _, _, errno := syscall.Syscall(unix.SYS_IOCTL, uintptr(fd), unix.TCGETS2, uintptr(unsafe.Pointer(&term))); errno != 0 { + return nil, fmt.Errorf("failed to get console termios: %v", errno) + } + // no parity bit, only need 1 stop bit, no hardware flowcontrol + term.Cflag &^= unix.CBAUD | unix.CSIZE | unix.PARENB | unix.CSTOPB | unix.CRTSCTS + // ignore modem controls + term.Cflag |= unix.B115200 | unix.CS8 | unix.CLOCAL | unix.CREAD + // setup for non-canonical mode + term.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON + term.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN + term.Oflag &^= unix.OPOST + term.Cc[unix.VMIN] = 0 + term.Cc[unix.VTIME] = 10 // 1 second timeout + if _, _, errno := syscall.Syscall(unix.SYS_IOCTL, uintptr(fd), unix.TCSETS2, uintptr(unsafe.Pointer(&term))); errno != 0 { + return nil, fmt.Errorf("failed to get console termios: %v", errno) + } + tmp := fd + fd = -1 + return &tty{fd: tmp}, nil +} + +type tty struct { + mu sync.Mutex + fd int +} + +func (t *tty) Read(buf []byte) (int, error) { + t.mu.Lock() + defer t.mu.Unlock() + if t.fd == -1 { + return 0, io.EOF + } + n, err := syscall.Read(t.fd, buf) + if n < 0 { + n = 0 + } + return n, err +} + +func (t *tty) Close() error { + t.mu.Lock() + defer t.mu.Unlock() + if t.fd != -1 { + syscall.Close(t.fd) + t.fd = -1 + } + return nil +} + +// OpenAdbConsole provides fallback console output using 'adb shell dmesg -w'. +func OpenAdbConsole(bin, dev string) (rc io.ReadCloser, err error) { + rpipe, wpipe, err := osutil.LongPipe() + if err != nil { + return nil, err + } + cmd := exec.Command(bin, "-s", dev, "shell", "dmesg -w") + cmd.Stdout = wpipe + cmd.Stderr = wpipe + if err := cmd.Start(); err != nil { + rpipe.Close() + wpipe.Close() + return nil, fmt.Errorf("failed to start adb: %v", err) + } + wpipe.Close() + con := &adbCon{ + cmd: cmd, + rpipe: rpipe, + } + return con, err +} + +type adbCon struct { + closeMu sync.Mutex + readMu sync.Mutex + cmd *exec.Cmd + rpipe io.ReadCloser +} + +func (t *adbCon) Read(buf []byte) (int, error) { + t.readMu.Lock() + n, err := t.rpipe.Read(buf) + t.readMu.Unlock() + return n, err +} + +func (t *adbCon) Close() error { + t.closeMu.Lock() + cmd := t.cmd + t.cmd = nil + t.closeMu.Unlock() + if cmd == nil { + return nil + } + + cmd.Process.Kill() + + t.readMu.Lock() + t.rpipe.Close() + t.readMu.Unlock() + + cmd.Process.Wait() + return nil +} diff --git a/vm/vmimpl/merger.go b/vm/vmimpl/merger.go new file mode 100644 index 000000000..17b837602 --- /dev/null +++ b/vm/vmimpl/merger.go @@ -0,0 +1,81 @@ +// Copyright 2016 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 + +import ( + "bytes" + "fmt" + "io" + "sync" +) + +type OutputMerger struct { + Output chan []byte + Err chan error + teeMu sync.Mutex + tee io.Writer + wg sync.WaitGroup +} + +func NewOutputMerger(tee io.Writer) *OutputMerger { + return &OutputMerger{ + Output: make(chan []byte, 1000), + Err: make(chan error, 1), + tee: tee, + } +} + +func (merger *OutputMerger) Wait() { + merger.wg.Wait() + close(merger.Output) +} + +func (merger *OutputMerger) Add(name string, r io.ReadCloser) { + merger.wg.Add(1) + go func() { + var pending []byte + var buf [4 << 10]byte + for { + n, err := r.Read(buf[:]) + if n != 0 { + pending = append(pending, buf[:n]...) + if pos := bytes.LastIndexByte(pending, '\n'); pos != -1 { + out := pending[:pos+1] + if merger.tee != nil { + merger.teeMu.Lock() + merger.tee.Write(out) + merger.teeMu.Unlock() + } + select { + case merger.Output <- append([]byte{}, out...): + r := copy(pending[:], pending[pos+1:]) + pending = pending[:r] + default: + } + } + } + if err != nil { + if len(pending) != 0 { + pending = append(pending, '\n') + if merger.tee != nil { + merger.teeMu.Lock() + merger.tee.Write(pending) + merger.teeMu.Unlock() + } + select { + case merger.Output <- pending: + default: + } + } + r.Close() + select { + case merger.Err <- fmt.Errorf("failed to read from %v: %v", name, err): + default: + } + merger.wg.Done() + return + } + } + }() +} diff --git a/vm/vmimpl/merger_test.go b/vm/vmimpl/merger_test.go new file mode 100644 index 000000000..335dcb228 --- /dev/null +++ b/vm/vmimpl/merger_test.go @@ -0,0 +1,79 @@ +// Copyright 2016 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 + +import ( + "bytes" + "testing" + "time" + + "github.com/google/syzkaller/pkg/osutil" +) + +func TestMerger(t *testing.T) { + tee := new(bytes.Buffer) + merger := NewOutputMerger(tee) + + rp1, wp1, err := osutil.LongPipe() + if err != nil { + t.Fatal(err) + } + defer wp1.Close() + merger.Add("pipe1", rp1) + + rp2, wp2, err := osutil.LongPipe() + if err != nil { + t.Fatal(err) + } + defer wp2.Close() + merger.Add("pipe2", rp2) + + wp1.Write([]byte("111")) + select { + case <-merger.Output: + t.Fatalf("merger produced incomplete line") + case <-time.After(10 * time.Millisecond): + } + + wp2.Write([]byte("222")) + select { + case <-merger.Output: + t.Fatalf("merger produced incomplete line") + case <-time.After(10 * time.Millisecond): + } + + wp1.Write([]byte("333\n444")) + got := string(<-merger.Output) + if want := "111333\n"; got != want { + t.Fatalf("bad line: '%s', want '%s'", got, want) + } + + wp2.Write([]byte("555\n666\n777")) + got = string(<-merger.Output) + if want := "222555\n666\n"; got != want { + t.Fatalf("bad line: '%s', want '%s'", got, want) + } + + wp1.Close() + got = string(<-merger.Output) + if want := "444\n"; got != want { + t.Fatalf("bad line: '%s', want '%s'", got, want) + } + + if err := <-merger.Err; err == nil || err.Error() != "failed to read from pipe1: EOF" { + t.Fatalf("merger did not produce io.EOF: %v", err) + } + + wp2.Close() + got = string(<-merger.Output) + if want := "777\n"; got != want { + t.Fatalf("bad line: '%s', want '%s'", got, want) + } + + merger.Wait() + want := "111333\n222555\n666\n444\n777\n" + if got := string(tee.Bytes()); got != want { + t.Fatalf("bad tee: '%s', want '%s'", got, want) + } +} diff --git a/vm/vmimpl/util.go b/vm/vmimpl/util.go new file mode 100644 index 000000000..f80de8ab1 --- /dev/null +++ b/vm/vmimpl/util.go @@ -0,0 +1,19 @@ +// 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 + +import ( + "time" +) + +// Sleep for d. +// If shutdown is in progress, return false prematurely. +func SleepInterruptible(d time.Duration) bool { + select { + case <-time.After(d): + return true + case <-Shutdown: + return false + } +} diff --git a/vm/vmimpl/vmimpl.go b/vm/vmimpl/vmimpl.go new file mode 100644 index 000000000..4c542429c --- /dev/null +++ b/vm/vmimpl/vmimpl.go @@ -0,0 +1,77 @@ +// 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" + "time" +) + +// 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.TimeoutErr. + // 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) + + // 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 + Workdir string + Image string + Debug bool + Config []byte // json-serialized VM-type-specific config +} + +// 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{}) + TimeoutErr = errors.New("timeout") + + ctors = make(map[string]ctorFunc) +) + +type ctorFunc func(env *Env) (Pool, error) |
