github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/vm/vmimpl/console.go (about)

     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  //go:build !windows
     4  
     5  package vmimpl
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"os/exec"
    11  	"sync"
    12  	"syscall"
    13  
    14  	"github.com/google/syzkaller/pkg/osutil"
    15  	"golang.org/x/sys/unix"
    16  )
    17  
    18  // Tested on Suzy-Q and BeagleBone.
    19  func 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: %w", err)
    23  	}
    24  	defer func() {
    25  		if fd != -1 {
    26  			syscall.Close(fd)
    27  		}
    28  	}()
    29  	term, err := unix.IoctlGetTermios(fd, syscallTCGETS)
    30  	if err != nil {
    31  		return nil, fmt.Errorf("failed to get console termios: %w", err)
    32  	}
    33  	// No parity bit, only need 1 stop bit, no hardware flowcontrol,
    34  	term.Cflag &^= unixCBAUD | unix.CSIZE | unix.PARENB | unix.CSTOPB | unixCRTSCTS
    35  	// Ignore modem controls.
    36  	term.Cflag |= unix.B115200 | unix.CS8 | unix.CLOCAL | unix.CREAD
    37  	// Setup for non-canonical mode.
    38  	term.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR |
    39  		unix.IGNCR | unix.ICRNL | unix.IXON
    40  	term.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
    41  	term.Oflag &^= unix.OPOST
    42  	term.Cc[unix.VMIN] = 0
    43  	term.Cc[unix.VTIME] = 10 // 1 second timeout
    44  	if err = unix.IoctlSetTermios(fd, syscallTCSETS, term); err != nil {
    45  		return nil, fmt.Errorf("failed to get console termios: %w", err)
    46  	}
    47  	tmp := fd
    48  	fd = -1
    49  	return &tty{fd: tmp}, nil
    50  }
    51  
    52  type tty struct {
    53  	mu sync.Mutex
    54  	fd int
    55  }
    56  
    57  func (t *tty) Read(buf []byte) (int, error) {
    58  	t.mu.Lock()
    59  	defer t.mu.Unlock()
    60  	if t.fd == -1 {
    61  		return 0, io.EOF
    62  	}
    63  	n, err := syscall.Read(t.fd, buf)
    64  	if n < 0 {
    65  		n = 0
    66  	}
    67  	return n, err
    68  }
    69  
    70  func (t *tty) Close() error {
    71  	t.mu.Lock()
    72  	defer t.mu.Unlock()
    73  	if t.fd != -1 {
    74  		syscall.Close(t.fd)
    75  		t.fd = -1
    76  	}
    77  	return nil
    78  }
    79  
    80  // OpenRemoteKernelLog accesses to the host where Android VM runs on, not Android VM itself.
    81  // The host stores all kernel outputs of Android VM so in case of crashes nothing will be lost.
    82  func OpenRemoteKernelLog(ip, console string) (rc io.ReadCloser, err error) {
    83  	rpipe, wpipe, err := osutil.LongPipe()
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	conAddr := "vsoc-01@" + ip
    88  	cmd := osutil.Command("ssh", conAddr, "tail", "-f", console)
    89  	cmd.Stdout = wpipe
    90  	cmd.Stderr = wpipe
    91  	if _, err := cmd.StdinPipe(); err != nil {
    92  		rpipe.Close()
    93  		wpipe.Close()
    94  		return nil, err
    95  	}
    96  	if err := cmd.Start(); err != nil {
    97  		rpipe.Close()
    98  		wpipe.Close()
    99  		return nil, fmt.Errorf("failed to connect to console server: %w", err)
   100  	}
   101  	wpipe.Close()
   102  	con := &remoteCon{
   103  		cmd:   cmd,
   104  		rpipe: rpipe,
   105  	}
   106  	return con, nil
   107  }
   108  
   109  // Open dmesg remotely.
   110  func OpenRemoteConsole(bin string, args ...string) (rc io.ReadCloser, err error) {
   111  	rpipe, wpipe, err := osutil.LongPipe()
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	args = append(args, "dmesg -w")
   116  	cmd := osutil.Command(bin, args...)
   117  	cmd.Stdout = wpipe
   118  	cmd.Stderr = wpipe
   119  	if err := cmd.Start(); err != nil {
   120  		rpipe.Close()
   121  		wpipe.Close()
   122  		return nil, fmt.Errorf("failed to start adb: %w", err)
   123  	}
   124  	wpipe.Close()
   125  	con := &remoteCon{
   126  		cmd:   cmd,
   127  		rpipe: rpipe,
   128  	}
   129  	return con, err
   130  }
   131  
   132  // OpenAdbConsole provides fallback console output using 'adb shell dmesg -w'.
   133  func OpenAdbConsole(bin, dev string) (rc io.ReadCloser, err error) {
   134  	return OpenRemoteConsole(bin, "-s", dev, "shell")
   135  }
   136  
   137  type remoteCon struct {
   138  	closeMu sync.Mutex
   139  	readMu  sync.Mutex
   140  	cmd     *exec.Cmd
   141  	rpipe   io.ReadCloser
   142  }
   143  
   144  func (t *remoteCon) Read(buf []byte) (int, error) {
   145  	t.readMu.Lock()
   146  	n, err := t.rpipe.Read(buf)
   147  	t.readMu.Unlock()
   148  	return n, err
   149  }
   150  
   151  func (t *remoteCon) Close() error {
   152  	t.closeMu.Lock()
   153  	cmd := t.cmd
   154  	t.cmd = nil
   155  	t.closeMu.Unlock()
   156  	if cmd == nil {
   157  		return nil
   158  	}
   159  
   160  	cmd.Process.Kill()
   161  
   162  	t.readMu.Lock()
   163  	t.rpipe.Close()
   164  	t.readMu.Unlock()
   165  
   166  	cmd.Process.Wait()
   167  	return nil
   168  }