Skip to content

Commit

Permalink
serial: Add support for NewFileHandleSerialPortAttachment
Browse files Browse the repository at this point in the history
This makes it possible to have an interactive serial console.
  • Loading branch information
cfergeau authored and anjannath committed Apr 19, 2023
1 parent cdb7c81 commit 4979be1
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 9 deletions.
2 changes: 2 additions & 0 deletions doc/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,11 @@ The `--device virtio-net` option adds a network interface to the virtual machine
#### Description

The `--device virtio-serial` option adds a serial device to the virtual machine. This is useful to redirect text output from the virtual machine to a log file.
The `logFilePath` and `stdio` arguments are mutually exclusive.

#### Arguments
- `logFilePath`: path where the serial port output should be written.
- `stdio`: uses stdin/stdout for the serial console input/output.

#### Example
`--device virtio-serial,logFilePath=/Users/virtuser/vfkit.log`
Expand Down
36 changes: 32 additions & 4 deletions pkg/config/virtio.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ type VirtioNet struct {

// VirtioSerial configures the virtual machine serial ports.
type VirtioSerial struct {
LogFile string
LogFile string
UsesStdio bool
}

// TODO: Add VirtioBalloon
Expand Down Expand Up @@ -134,10 +135,31 @@ func VirtioSerialNew(logFilePath string) (VirtioDevice, error) {
}, nil
}

func VirtioSerialNewStdio() (VirtioDevice, error) {
return &VirtioSerial{
UsesStdio: true,
}, nil
}

func (dev *VirtioSerial) validate() error {
if dev.LogFile != "" && dev.UsesStdio {
return fmt.Errorf("'logFilePath' and 'stdio' cannot be set at the same time")
}
if dev.LogFile == "" && !dev.UsesStdio {
return fmt.Errorf("One of 'logFilePath' or 'stdio' must be set")
}

return nil
}

func (dev *VirtioSerial) ToCmdLine() ([]string, error) {
if dev.LogFile == "" {
return nil, fmt.Errorf("virtio-serial needs the path to the log file")
if err := dev.validate(); err != nil {
return nil, err
}
if dev.UsesStdio {
return []string{"--device", "virtio-serial,stdio"}, nil
}

return []string{"--device", fmt.Sprintf("virtio-serial,logFilePath=%s", dev.LogFile)}, nil
}

Expand All @@ -146,11 +168,17 @@ func (dev *VirtioSerial) FromOptions(options []option) error {
switch option.key {
case "logFilePath":
dev.LogFile = option.value
case "stdio":
if option.value != "" {
return fmt.Errorf("Unexpected value for virtio-serial 'stdio' option: %s", option.value)
}
dev.UsesStdio = true
default:
return fmt.Errorf("Unknown option for virtio-serial devices: %s", option.key)
}
}
return nil

return dev.validate()
}

// VirtioNetNew creates a new network device for the virtual machine. It will
Expand Down
7 changes: 7 additions & 0 deletions pkg/config/virtio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ var virtioDevTests = map[string]virtioDevTest{
},
expectedCmdLine: []string{"--device", "virtio-serial,logFilePath=/foo/bar.log"},
},
"NewVirtioSerialStdio": {
newDev: func() (VirtioDevice, error) { return VirtioSerialNewStdio() },
expectedDev: &VirtioSerial{
UsesStdio: true,
},
expectedCmdLine: []string{"--device", "virtio-serial,stdio"},
},
"NewVirtioNet": {
newDev: func() (VirtioDevice, error) { return VirtioNetNew("") },
expectedDev: &VirtioNet{
Expand Down
42 changes: 37 additions & 5 deletions pkg/vf/virtio.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ package vf
import (
"fmt"
"net"
"os"
"path/filepath"
"syscall"

"github.com/crc-org/vfkit/pkg/config"

"github.com/Code-Hex/vz/v3"
log "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)

type VirtioBlk config.VirtioBlk
Expand Down Expand Up @@ -155,17 +158,46 @@ func (dev *VirtioRng) AddToVirtualMachineConfig(vmConfig *vzVirtualMachineConfig
return nil
}

// https://developer.apple.com/documentation/virtualization/running_linux_in_a_virtual_machine?language=objc#:~:text=Configure%20the%20Serial%20Port%20Device%20for%20Standard%20In%20and%20Out
func setRawMode(f *os.File) {
// Get settings for terminal
attr, _ := unix.IoctlGetTermios(int(f.Fd()), unix.TIOCGETA)

// Put stdin into raw mode, disabling local echo, input canonicalization,
// and CR-NL mapping.
attr.Iflag &^= syscall.ICRNL
attr.Lflag &^= syscall.ICANON | syscall.ECHO

// Set minimum characters when reading = 1 char
attr.Cc[syscall.VMIN] = 1

// set timeout when reading as non-canonical mode
attr.Cc[syscall.VTIME] = 0

// reflects the changed settings
unix.IoctlSetTermios(int(f.Fd()), unix.TIOCSETA, attr)
}

func (dev *VirtioSerial) AddToVirtualMachineConfig(vmConfig *vzVirtualMachineConfiguration) error {
if dev.LogFile == "" {
return fmt.Errorf("missing mandatory 'logFile' option for virtio-serial device")
if dev.LogFile != "" {
log.Infof("Adding virtio-serial device (logFile: %s)", dev.LogFile)
}
if dev.UsesStdio {
log.Infof("Adding stdio console")
}
log.Infof("Adding virtio-serial device (logFile: %s)", dev.LogFile)

//serialPortAttachment := vz.NewFileHandleSerialPortAttachment(os.Stdin, tty)
serialPortAttachment, err := vz.NewFileSerialPortAttachment(dev.LogFile, false)
var serialPortAttachment vz.SerialPortAttachment
var err error
if dev.UsesStdio {
setRawMode(os.Stdin)
serialPortAttachment, err = vz.NewFileHandleSerialPortAttachment(os.Stdin, os.Stdout)
} else {
serialPortAttachment, err = vz.NewFileSerialPortAttachment(dev.LogFile, false)
}
if err != nil {
return err
}

consoleConfig, err := vz.NewVirtioConsoleDeviceSerialPortConfiguration(serialPortAttachment)
if err != nil {
return err
Expand Down

0 comments on commit 4979be1

Please sign in to comment.