github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/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  	return max(n, 0), err
    65  }
    66  
    67  func (t *tty) Close() error {
    68  	t.mu.Lock()
    69  	defer t.mu.Unlock()
    70  	if t.fd != -1 {
    71  		syscall.Close(t.fd)
    72  		t.fd = -1
    73  	}
    74  	return nil
    75  }
    76  
    77  // OpenRemoteKernelLog accesses to the host where Android VM runs on, not Android VM itself.
    78  // The host stores all kernel outputs of Android VM so in case of crashes nothing will be lost.
    79  func OpenRemoteKernelLog(ip, console string) (rc io.ReadCloser, err error) {
    80  	conAddr := "vsoc-01@" + ip
    81  	args := []string{
    82  		conAddr,
    83  		"tail",
    84  		"-f",
    85  		console,
    86  	}
    87  	return OpenConsoleByCmd("ssh", args)
    88  }
    89  
    90  // Open dmesg remotely.
    91  func OpenRemoteConsole(bin string, args ...string) (rc io.ReadCloser, err error) {
    92  	args = append(args, "dmesg -w")
    93  	return OpenConsoleByCmd(bin, args)
    94  }
    95  
    96  // OpenAdbConsole provides fallback console output using 'adb shell dmesg -w'.
    97  func OpenAdbConsole(bin, dev string) (rc io.ReadCloser, err error) {
    98  	return OpenRemoteConsole(bin, "-s", dev, "shell")
    99  }
   100  
   101  // Open console log by cmd.
   102  func OpenConsoleByCmd(bin string, args []string) (rc io.ReadCloser, err error) {
   103  	rpipe, wpipe, err := osutil.LongPipe()
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	cmd := osutil.Command(bin, args...)
   108  	cmd.Stdout = wpipe
   109  	cmd.Stderr = wpipe
   110  	if _, err := cmd.StdinPipe(); err != nil {
   111  		rpipe.Close()
   112  		wpipe.Close()
   113  		return nil, err
   114  	}
   115  	if err := cmd.Start(); err != nil {
   116  		rpipe.Close()
   117  		wpipe.Close()
   118  		return nil, fmt.Errorf("failed to open console: %w", err)
   119  	}
   120  	wpipe.Close()
   121  	con := &remoteCon{
   122  		cmd:   cmd,
   123  		rpipe: rpipe,
   124  	}
   125  	return con, err
   126  }
   127  
   128  type remoteCon struct {
   129  	closeMu sync.Mutex
   130  	readMu  sync.Mutex
   131  	cmd     *exec.Cmd
   132  	rpipe   io.ReadCloser
   133  }
   134  
   135  func (t *remoteCon) Read(buf []byte) (int, error) {
   136  	t.readMu.Lock()
   137  	n, err := t.rpipe.Read(buf)
   138  	t.readMu.Unlock()
   139  	return n, err
   140  }
   141  
   142  func (t *remoteCon) Close() error {
   143  	t.closeMu.Lock()
   144  	cmd := t.cmd
   145  	t.cmd = nil
   146  	t.closeMu.Unlock()
   147  	if cmd == nil {
   148  		return nil
   149  	}
   150  
   151  	cmd.Process.Kill()
   152  
   153  	t.readMu.Lock()
   154  	t.rpipe.Close()
   155  	t.readMu.Unlock()
   156  
   157  	cmd.Process.Wait()
   158  	return nil
   159  }