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
4package vmimpl
5
6import (
7	"fmt"
8	"io"
9	"os/exec"
10	"sync"
11	"syscall"
12	"unsafe"
13
14	"github.com/google/syzkaller/pkg/osutil"
15	"golang.org/x/sys/unix"
16)
17
18// Tested on Suzy-Q and BeagleBone.
19func OpenConsole(con string) (rc io.ReadCloser, err error) {
20	fd, err := syscall.Open(con, syscall.O_RDONLY|syscall.O_NOCTTY|syscall.O_SYNC, 0)
21	if err != nil {
22		return nil, fmt.Errorf("failed to open console file: %v", err)
23	}
24	defer func() {
25		if fd != -1 {
26			syscall.Close(fd)
27		}
28	}()
29	var term unix.Termios
30	_, _, errno := syscall.Syscall(unix.SYS_IOCTL, uintptr(fd), syscallTCGETS, uintptr(unsafe.Pointer(&term)))
31	if errno != 0 {
32		return nil, fmt.Errorf("failed to get console termios: %v", errno)
33	}
34	// no parity bit, only need 1 stop bit, no hardware flowcontrol
35	term.Cflag &^= unixCBAUD | unix.CSIZE | unix.PARENB | unix.CSTOPB | unixCRTSCTS
36	// ignore modem controls
37	term.Cflag |= unix.B115200 | unix.CS8 | unix.CLOCAL | unix.CREAD
38	// setup for non-canonical mode
39	term.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR |
40		unix.IGNCR | unix.ICRNL | unix.IXON
41	term.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
42	term.Oflag &^= unix.OPOST
43	term.Cc[unix.VMIN] = 0
44	term.Cc[unix.VTIME] = 10 // 1 second timeout
45	_, _, errno = syscall.Syscall(unix.SYS_IOCTL, uintptr(fd), syscallTCSETS, uintptr(unsafe.Pointer(&term)))
46	if errno != 0 {
47		return nil, fmt.Errorf("failed to get console termios: %v", errno)
48	}
49	tmp := fd
50	fd = -1
51	return &tty{fd: tmp}, nil
52}
53
54type tty struct {
55	mu sync.Mutex
56	fd int
57}
58
59func (t *tty) Read(buf []byte) (int, error) {
60	t.mu.Lock()
61	defer t.mu.Unlock()
62	if t.fd == -1 {
63		return 0, io.EOF
64	}
65	n, err := syscall.Read(t.fd, buf)
66	if n < 0 {
67		n = 0
68	}
69	return n, err
70}
71
72func (t *tty) Close() error {
73	t.mu.Lock()
74	defer t.mu.Unlock()
75	if t.fd != -1 {
76		syscall.Close(t.fd)
77		t.fd = -1
78	}
79	return nil
80}
81
82// Open dmesg remotely
83func OpenRemoteConsole(bin string, args ...string) (rc io.ReadCloser, err error) {
84	rpipe, wpipe, err := osutil.LongPipe()
85	if err != nil {
86		return nil, err
87	}
88	args = append(args, "dmesg -w")
89	cmd := osutil.Command(bin, args...)
90	cmd.Stdout = wpipe
91	cmd.Stderr = wpipe
92	if err := cmd.Start(); err != nil {
93		rpipe.Close()
94		wpipe.Close()
95		return nil, fmt.Errorf("failed to start adb: %v", err)
96	}
97	wpipe.Close()
98	con := &remoteCon{
99		cmd:   cmd,
100		rpipe: rpipe,
101	}
102	return con, err
103}
104
105// OpenAdbConsole provides fallback console output using 'adb shell dmesg -w'.
106func OpenAdbConsole(bin, dev string) (rc io.ReadCloser, err error) {
107	return OpenRemoteConsole(bin, "-s", dev, "shell")
108}
109
110type remoteCon struct {
111	closeMu sync.Mutex
112	readMu  sync.Mutex
113	cmd     *exec.Cmd
114	rpipe   io.ReadCloser
115}
116
117func (t *remoteCon) Read(buf []byte) (int, error) {
118	t.readMu.Lock()
119	n, err := t.rpipe.Read(buf)
120	t.readMu.Unlock()
121	return n, err
122}
123
124func (t *remoteCon) Close() error {
125	t.closeMu.Lock()
126	cmd := t.cmd
127	t.cmd = nil
128	t.closeMu.Unlock()
129	if cmd == nil {
130		return nil
131	}
132
133	cmd.Process.Kill()
134
135	t.readMu.Lock()
136	t.rpipe.Close()
137	t.readMu.Unlock()
138
139	cmd.Process.Wait()
140	return nil
141}
142