// 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 (
"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
_, _, errno := syscall.Syscall(unix.SYS_IOCTL, uintptr(fd), syscallTCGETS, uintptr(unsafe.Pointer(&term)))
if 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 &^= unixCBAUD | unix.CSIZE | unix.PARENB | unix.CSTOPB | unixCRTSCTS
// 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
_, _, errno = syscall.Syscall(unix.SYS_IOCTL, uintptr(fd), syscallTCSETS, uintptr(unsafe.Pointer(&term)))
if 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
}
// Open dmesg remotely
func OpenRemoteConsole(bin string, args ...string) (rc io.ReadCloser, err error) {
rpipe, wpipe, err := osutil.LongPipe()
if err != nil {
return nil, err
}
args = append(args, "dmesg -w")
cmd := osutil.Command(bin, args...)
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 := &remoteCon{
cmd: cmd,
rpipe: rpipe,
}
return con, err
}
// OpenAdbConsole provides fallback console output using 'adb shell dmesg -w'.
func OpenAdbConsole(bin, dev string) (rc io.ReadCloser, err error) {
return OpenRemoteConsole(bin, "-s", dev, "shell")
}
type remoteCon struct {
closeMu sync.Mutex
readMu sync.Mutex
cmd *exec.Cmd
rpipe io.ReadCloser
}
func (t *remoteCon) Read(buf []byte) (int, error) {
t.readMu.Lock()
n, err := t.rpipe.Read(buf)
t.readMu.Unlock()
return n, err
}
func (t *remoteCon) 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
}