aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2016-12-08 20:54:09 +0100
committerDmitry Vyukov <dvyukov@google.com>2016-12-19 15:56:10 +0100
commita074da17a4055352fea94afbd5a15c53d0946653 (patch)
tree8c53251fdfe95dab163fa38feebf2a9406be5664
parent53366f457199a1a869ceeb461d637e1a33dc21d1 (diff)
vm/adb: support BeagleBone console
BeagleBone console requires some special tty-ism to work. Fortunately, this code also works with Suzy-Q.
-rw-r--r--vm/adb/adb.go73
-rw-r--r--vm/adb/console.go75
-rw-r--r--vm/gce/gce.go30
-rw-r--r--vm/merger.go9
-rw-r--r--vm/merger_test.go8
-rw-r--r--vm/qemu/qemu.go17
6 files changed, 119 insertions, 93 deletions
diff --git a/vm/adb/adb.go b/vm/adb/adb.go
index f16a01e0e..77f7489c9 100644
--- a/vm/adb/adb.go
+++ b/vm/adb/adb.go
@@ -47,7 +47,7 @@ func ctor(cfg *vm.Config) (vm.Instance, error) {
return nil, err
}
var err error
- if inst.console, err = findConsole(inst.cfg.Device); err != nil {
+ if inst.console, err = findConsole(inst.cfg.Bin, inst.cfg.Device); err != nil {
return nil, err
}
if err := inst.checkBatteryLevel(); err != nil {
@@ -83,7 +83,7 @@ var (
// while Suzy-Q console uses the same USB calbe as adb.
// The overall idea is as follows. We use 'adb shell' to write a unique string onto console,
// then we read from all console devices and see on what console the unique string appears.
-func findConsole(dev string) (string, error) {
+func findConsole(adb, dev string) (string, error) {
consoleCacheMu.Lock()
defer consoleCacheMu.Unlock()
if con := devToConsole[dev]; con != "" {
@@ -103,20 +103,17 @@ func findConsole(dev string) (string, error) {
out := new([]byte)
output[con] = out
go func(con string) {
- cmd := exec.Command("cat", con)
- stdout, err := cmd.StdoutPipe()
+ tty, err := openConsole(con)
if err != nil {
errors <- err
+ return
}
- if cmd.Start() != nil {
- errors <- err
- }
+ defer tty.Close()
go func() {
<-done
- cmd.Process.Kill()
+ tty.Close()
}()
- *out, _ = ioutil.ReadAll(stdout)
- cmd.Wait()
+ *out, _ = ioutil.ReadAll(tty)
errors <- nil
}(con)
}
@@ -125,7 +122,7 @@ func findConsole(dev string) (string, error) {
}
time.Sleep(500 * time.Millisecond)
unique := fmt.Sprintf(">>>%v<<<", dev)
- cmd := exec.Command("adb", "-s", dev, "shell", "echo", "\"", unique, "\"", ">", "/dev/kmsg")
+ cmd := exec.Command(adb, "-s", dev, "shell", "echo", "\"", unique, "\"", ">", "/dev/kmsg")
if out, err := cmd.CombinedOutput(); err != nil {
return "", fmt.Errorf("failed to run adb shell: %v\n%s", err, out)
}
@@ -329,34 +326,14 @@ func (inst *instance) Copy(hostSrc string) (string, error) {
}
func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command string) (<-chan []byte, <-chan error, error) {
- catRpipe, catWpipe, err := vm.LongPipe()
+ tty, err := openConsole(inst.console)
if err != nil {
return nil, nil, err
}
- cat := exec.Command("cat", inst.console)
- cat.Stdout = catWpipe
- cat.Stderr = catWpipe
- if err := cat.Start(); err != nil {
- catRpipe.Close()
- catWpipe.Close()
- return nil, nil, fmt.Errorf("failed to start cat %v: %v", inst.console, err)
-
- }
- catWpipe.Close()
- catDone := make(chan error, 1)
- go func() {
- err := cat.Wait()
- if inst.cfg.Debug {
- Logf(0, "cat exited: %v", err)
- }
- catDone <- fmt.Errorf("cat exited: %v", err)
- }()
-
adbRpipe, adbWpipe, err := vm.LongPipe()
if err != nil {
- cat.Process.Kill()
- catRpipe.Close()
+ tty.Close()
return nil, nil, err
}
if inst.cfg.Debug {
@@ -366,29 +343,20 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
adb.Stdout = adbWpipe
adb.Stderr = adbWpipe
if err := adb.Start(); err != nil {
- cat.Process.Kill()
- catRpipe.Close()
+ tty.Close()
adbRpipe.Close()
adbWpipe.Close()
return nil, nil, fmt.Errorf("failed to start adb: %v", err)
}
adbWpipe.Close()
- adbDone := make(chan error, 1)
- go func() {
- err := adb.Wait()
- if inst.cfg.Debug {
- Logf(0, "adb exited: %v", err)
- }
- adbDone <- fmt.Errorf("adb exited: %v", err)
- }()
var tee io.Writer
if inst.cfg.Debug {
tee = os.Stdout
}
merger := vm.NewOutputMerger(tee)
- merger.Add(catRpipe)
- merger.Add(adbRpipe)
+ merger.Add("console", tty)
+ merger.Add("adb", adbRpipe)
errc := make(chan error, 1)
signal := func(err error) {
@@ -402,27 +370,20 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
select {
case <-time.After(timeout):
signal(vm.TimeoutErr)
- cat.Process.Kill()
- adb.Process.Kill()
case <-stop:
signal(vm.TimeoutErr)
- cat.Process.Kill()
- adb.Process.Kill()
case <-inst.closed:
if inst.cfg.Debug {
Logf(0, "instance closed")
}
signal(fmt.Errorf("instance closed"))
- cat.Process.Kill()
- adb.Process.Kill()
- case err := <-catDone:
- signal(err)
- adb.Process.Kill()
- case err := <-adbDone:
+ case err := <-merger.Err:
signal(err)
- cat.Process.Kill()
}
+ tty.Close()
+ adb.Process.Kill()
merger.Wait()
+ adb.Wait()
}()
return merger.Output, errc, nil
}
diff --git a/vm/adb/console.go b/vm/adb/console.go
new file mode 100644
index 000000000..e55e019ae
--- /dev/null
+++ b/vm/adb/console.go
@@ -0,0 +1,75 @@
+// 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 adb
+
+import (
+ "fmt"
+ "io"
+ "sync"
+ "syscall"
+ "unsafe"
+
+ . "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 Termios
+ if _, _, errno := syscall.Syscall(SYS_IOCTL, uintptr(fd), 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 &^= CBAUD | CSIZE | PARENB | CSTOPB | CRTSCTS
+ // ignore modem controls
+ term.Cflag |= B115200 | CS8 | CLOCAL | CREAD
+ // setup for non-canonical mode
+ term.Iflag &^= IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON
+ term.Lflag &^= ECHO | ECHONL | ICANON | ISIG | IEXTEN
+ term.Oflag &^= OPOST
+ term.Cc[VMIN] = 0
+ term.Cc[VTIME] = 10 // 1 second timeout
+ if _, _, errno := syscall.Syscall(SYS_IOCTL, uintptr(fd), 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
+}
diff --git a/vm/gce/gce.go b/vm/gce/gce.go
index 03ceb10dd..edce6c38e 100644
--- a/vm/gce/gce.go
+++ b/vm/gce/gce.go
@@ -170,11 +170,6 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
}
conWpipe.Close()
- conDone := make(chan error, 1)
- go func() {
- err := con.Wait()
- conDone <- fmt.Errorf("console connection closed: %v", err)
- }()
sshRpipe, sshWpipe, err := vm.LongPipe()
if err != nil {
@@ -197,15 +192,10 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
return nil, nil, fmt.Errorf("failed to connect to instance: %v", err)
}
sshWpipe.Close()
- sshDone := make(chan error, 1)
- go func() {
- err := ssh.Wait()
- sshDone <- fmt.Errorf("ssh exited: %v", err)
- }()
merger := vm.NewOutputMerger(nil)
- merger.Add(conRpipe)
- merger.Add(sshRpipe)
+ merger.Add("console", conRpipe)
+ merger.Add("ssh", sshRpipe)
errc := make(chan error, 1)
signal := func(err error) {
@@ -219,20 +209,11 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
select {
case <-time.After(timeout):
signal(vm.TimeoutErr)
- con.Process.Kill()
- ssh.Process.Kill()
case <-stop:
signal(vm.TimeoutErr)
- con.Process.Kill()
- ssh.Process.Kill()
case <-inst.closed:
signal(fmt.Errorf("instance closed"))
- con.Process.Kill()
- ssh.Process.Kill()
- case err := <-conDone:
- signal(err)
- ssh.Process.Kill()
- case err := <-sshDone:
+ case err := <-merger.Err:
// Check if the instance was terminated due to preemption or host maintenance.
time.Sleep(5 * time.Second) // just to avoid any GCE races
if !GCE.IsInstanceRunning(inst.name) {
@@ -240,9 +221,12 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
err = vm.TimeoutErr
}
signal(err)
- con.Process.Kill()
}
+ con.Process.Kill()
+ ssh.Process.Kill()
merger.Wait()
+ con.Wait()
+ ssh.Wait()
}()
return merger.Output, errc, nil
}
diff --git a/vm/merger.go b/vm/merger.go
index 2effd0f45..c3b5c16d0 100644
--- a/vm/merger.go
+++ b/vm/merger.go
@@ -5,12 +5,14 @@ package vm
import (
"bytes"
+ "fmt"
"io"
"sync"
)
type OutputMerger struct {
Output chan []byte
+ Err chan error
tee io.Writer
wg sync.WaitGroup
}
@@ -18,6 +20,7 @@ type OutputMerger struct {
func NewOutputMerger(tee io.Writer) *OutputMerger {
return &OutputMerger{
Output: make(chan []byte, 1000),
+ Err: make(chan error, 1),
tee: tee,
}
}
@@ -27,7 +30,7 @@ func (merger *OutputMerger) Wait() {
close(merger.Output)
}
-func (merger *OutputMerger) Add(r io.ReadCloser) {
+func (merger *OutputMerger) Add(name string, r io.ReadCloser) {
merger.wg.Add(1)
go func() {
var pending []byte
@@ -61,6 +64,10 @@ func (merger *OutputMerger) Add(r io.ReadCloser) {
}
}
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/merger_test.go b/vm/merger_test.go
index 1f0b0ba87..35da35284 100644
--- a/vm/merger_test.go
+++ b/vm/merger_test.go
@@ -18,14 +18,14 @@ func TestMerger(t *testing.T) {
t.Fatal(err)
}
defer wp1.Close()
- merger.Add(rp1)
+ merger.Add("pipe1", rp1)
rp2, wp2, err := LongPipe()
if err != nil {
t.Fatal(err)
}
defer wp2.Close()
- merger.Add(rp2)
+ merger.Add("pipe2", rp2)
wp1.Write([]byte("111"))
select {
@@ -59,6 +59,10 @@ func TestMerger(t *testing.T) {
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 {
diff --git a/vm/qemu/qemu.go b/vm/qemu/qemu.go
index 6c2ac3b89..386e96b9a 100644
--- a/vm/qemu/qemu.go
+++ b/vm/qemu/qemu.go
@@ -222,7 +222,7 @@ func (inst *instance) Boot() error {
tee = os.Stdout
}
inst.merger = vm.NewOutputMerger(tee)
- inst.merger.Add(inst.rpipe)
+ inst.merger.Add("qemu", inst.rpipe)
inst.rpipe = nil
var bootOutput []byte
@@ -321,7 +321,7 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
if err != nil {
return nil, nil, err
}
- inst.merger.Add(rpipe)
+ inst.merger.Add("ssh", rpipe)
args := append(inst.sshArgs("-p"), "root@localhost", command)
if inst.cfg.Debug {
@@ -343,22 +343,17 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
}
}
- done := make(chan bool)
go func() {
select {
case <-time.After(timeout):
signal(vm.TimeoutErr)
- cmd.Process.Kill()
case <-stop:
signal(vm.TimeoutErr)
- cmd.Process.Kill()
- case <-done:
+ case err := <-inst.merger.Err:
+ signal(err)
}
- }()
- go func() {
- err := cmd.Wait()
- close(done)
- signal(err)
+ cmd.Process.Kill()
+ cmd.Wait()
}()
return inst.merger.Output, errc, nil
}