Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions internal/amd64vm/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const (
SnapshotBase = 0xd0009000
SnapshotSize = 0x1000

BalloonBase = 0xd000a000
BalloonSize = 0x1000
BalloonIRQ = 10

RootFSTag = vmruntime.RootFSTag
EmulatorTag = vmruntime.EmulatorTag
GuestCID = vmruntime.GuestCID
Expand Down
11 changes: 11 additions & 0 deletions internal/hv/kvm/balloon_linux_amd64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build linux && amd64

package kvm

func balloonTargetPages(mb uint64) uint32 {
pages := mb * 1024 * 1024 / 4096
if pages > uint64(^uint32(0)) {
return ^uint32(0)
}
return uint32(pages)
}
19 changes: 15 additions & 4 deletions internal/hv/kvm/boot_linux_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func bootToConditionWithNVMeBlock(ctx context.Context, kernel []byte, initrd []b
return serialOut.String(), err
}
case ExitMMIO:
if err := handleBootMMIOWithPCI(vm, 0, pci, fsdevs, vsock, rng, netdev, exit.MMIO); err != nil {
if err := handleBootMMIOWithPCI(vm, 0, pci, fsdevs, vsock, rng, nil, netdev, exit.MMIO); err != nil {
return serialOut.String(), err
}
case ExitHLT, ExitShutdown:
Expand Down Expand Up @@ -253,11 +253,11 @@ func handleUARTIO(uart *serial.UART8250, ioExit IOExit) error {
return nil
}

func handleBootMMIO(vm *VM, fsdevs []*virtio.FS, vsock *virtio.Vsock, rng *virtio.RNG, netdev *virtio.Net, mmio MMIOExit) error {
return handleBootMMIOForVCPU(vm, 0, fsdevs, vsock, rng, netdev, mmio)
func handleBootMMIO(vm *VM, fsdevs []*virtio.FS, vsock *virtio.Vsock, rng *virtio.RNG, balloon *virtio.Balloon, netdev *virtio.Net, mmio MMIOExit) error {
return handleBootMMIOForVCPU(vm, 0, fsdevs, vsock, rng, balloon, netdev, mmio)
}

func handleBootMMIOForVCPU(vm *VM, vcpuIndex int, fsdevs []*virtio.FS, vsock *virtio.Vsock, rng *virtio.RNG, netdev *virtio.Net, mmio MMIOExit) error {
func handleBootMMIOForVCPU(vm *VM, vcpuIndex int, fsdevs []*virtio.FS, vsock *virtio.Vsock, rng *virtio.RNG, balloon *virtio.Balloon, netdev *virtio.Net, mmio MMIOExit) error {
for _, fsdev := range fsdevs {
if fsdev == nil || !fsdev.Contains(mmio.Addr, int(mmio.Len)) {
continue
Expand Down Expand Up @@ -294,6 +294,17 @@ func handleBootMMIOForVCPU(vm *VM, vcpuIndex int, fsdevs []*virtio.FS, vsock *vi
vm.CompleteVCPUMMIORead(vcpuIndex, value, mmio.Len)
return nil
}
if balloon != nil && balloon.Contains(mmio.Addr, int(mmio.Len)) {
if mmio.Write {
return balloon.Write(mmio.Addr, int(mmio.Len), mmioValue(mmio))
}
value, err := balloon.Read(mmio.Addr, int(mmio.Len))
if err != nil {
return err
}
vm.CompleteVCPUMMIORead(vcpuIndex, value, mmio.Len)
return nil
}
if netdev != nil && netdev.Contains(mmio.Addr, int(mmio.Len)) {
if mmio.Write {
return netdev.Write(mmio.Addr, int(mmio.Len), mmioValue(mmio))
Expand Down
34 changes: 23 additions & 11 deletions internal/hv/kvm/exec_linux_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ func RunManagedExecWithFS(ctx context.Context, kernel []byte, initrd []byte, mem
}

func RunManagedExecWithFSAndNet(ctx context.Context, kernel []byte, initrd []byte, memoryMB uint64, cpus int, dmesg bool, fsdevs []*virtio.FS, netdev *virtio.Net, req client.ExecRequest) (client.ExecResponse, string, error) {
return RunManagedExecWithFSNetAndBalloon(ctx, kernel, initrd, memoryMB, 0, cpus, dmesg, fsdevs, netdev, req)
}

func RunManagedExecWithFSNetAndBalloon(ctx context.Context, kernel []byte, initrd []byte, memoryMB uint64, balloonMB uint64, cpus int, dmesg bool, fsdevs []*virtio.FS, netdev *virtio.Net, req client.ExecRequest) (client.ExecResponse, string, error) {
if len(req.Command) == 0 {
return client.ExecResponse{}, "", fmt.Errorf("exec command is required")
}
Expand All @@ -42,6 +46,12 @@ func RunManagedExecWithFSAndNet(ctx context.Context, kernel []byte, initrd []byt
vsock := virtio.NewVsock(amd64vm.VsockBase, amd64vm.VsockSize, amd64vm.VsockIRQ, vmruntime.GuestCID, backend)
defer vsock.Close()
rng := virtio.NewRNG(amd64vm.RNGBase, amd64vm.RNGSize, amd64vm.RNGIRQ)
balloon := virtio.NewBalloon(amd64vm.BalloonBase, amd64vm.BalloonSize, amd64vm.BalloonIRQ)
if targetPages := balloonTargetPages(balloonMB); targetPages != 0 {
if err := balloon.SetTargetPages(targetPages); err != nil {
return client.ExecResponse{}, "", fmt.Errorf("set balloon target: %w", err)
}
}

connCh := make(chan virtio.VsockConn, 1)
acceptErrCh := make(chan error, 1)
Expand Down Expand Up @@ -77,13 +87,15 @@ func RunManagedExecWithFSAndNet(ctx context.Context, kernel []byte, initrd []byt
}
vsock.Attach(vm, vm)
rng.Attach(vm, vm)
balloon.Attach(vm, vm)
if netdev != nil {
netdev.Attach(vm, vm)
}

extraCmdline := amd64vm.VirtioFSCommandLineArgs(fsdevs)
extraCmdline = append(extraCmdline, amd64vm.VirtioMMIODeviceArg(vsock.Base, vsock.IRQ))
extraCmdline = append(extraCmdline, amd64vm.VirtioMMIODeviceArg(rng.Base, rng.IRQ))
extraCmdline = append(extraCmdline, amd64vm.VirtioMMIODeviceArg(balloon.Base, balloon.IRQ))
if netdev != nil {
extraCmdline = append(extraCmdline, amd64vm.VirtioMMIODeviceArg(netdev.Base, netdev.IRQ))
}
Expand Down Expand Up @@ -132,7 +144,7 @@ func RunManagedExecWithFSAndNet(ctx context.Context, kernel []byte, initrd []byt
runDone := make(chan struct{})
go func() {
defer close(runDone)
setRunErr(runManagedExecVM(runCtx, vm, uart, fsdevs, vsock, rng, netdev, serialOut))
setRunErr(runManagedExecVM(runCtx, vm, uart, fsdevs, vsock, rng, balloon, netdev, serialOut))
}()
defer func() {
cancel()
Expand Down Expand Up @@ -196,16 +208,16 @@ func RunManagedExecWithFSAndNet(ctx context.Context, kernel []byte, initrd []byt
return client.ExecResponse{ExitCode: code, Output: output, Usage: usage}, serialOut.String(), nil
}

func runManagedExecVM(ctx context.Context, vm *VM, uart *serial.UART8250, fsdevs []*virtio.FS, vsock *virtio.Vsock, rng *virtio.RNG, netdev *virtio.Net, serialOut *vmruntime.SerialTranscript) error {
return runManagedExecVMWithSnapshot(ctx, vm, uart, fsdevs, vsock, rng, netdev, serialOut, nil)
func runManagedExecVM(ctx context.Context, vm *VM, uart *serial.UART8250, fsdevs []*virtio.FS, vsock *virtio.Vsock, rng *virtio.RNG, balloon *virtio.Balloon, netdev *virtio.Net, serialOut *vmruntime.SerialTranscript) error {
return runManagedExecVMWithSnapshot(ctx, vm, uart, fsdevs, vsock, rng, balloon, netdev, serialOut, nil)
}

func runManagedExecVMWithSnapshot(ctx context.Context, vm *VM, uart *serial.UART8250, fsdevs []*virtio.FS, vsock *virtio.Vsock, rng *virtio.RNG, netdev *virtio.Net, serialOut *vmruntime.SerialTranscript, snapshot *snapshotTrigger) error {
func runManagedExecVMWithSnapshot(ctx context.Context, vm *VM, uart *serial.UART8250, fsdevs []*virtio.FS, vsock *virtio.Vsock, rng *virtio.RNG, balloon *virtio.Balloon, netdev *virtio.Net, serialOut *vmruntime.SerialTranscript, snapshot *snapshotTrigger) error {
if vm != nil && len(vm.vcpus) > 1 {
if snapshot != nil {
return fmt.Errorf("KVM startup snapshots currently support only one vCPU")
}
return runManagedExecVMMulti(ctx, vm, uart, fsdevs, vsock, rng, netdev, serialOut)
return runManagedExecVMMulti(ctx, vm, uart, fsdevs, vsock, rng, balloon, netdev, serialOut)
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
Expand Down Expand Up @@ -243,7 +255,7 @@ func runManagedExecVMWithSnapshot(ctx context.Context, vm *VM, uart *serial.UART
} else if handled {
break
}
if err := handleBootMMIO(vm, fsdevs, vsock, rng, netdev, exit.MMIO); err != nil {
if err := handleBootMMIO(vm, fsdevs, vsock, rng, balloon, netdev, exit.MMIO); err != nil {
return err
}
case ExitShutdown:
Expand All @@ -254,13 +266,13 @@ func runManagedExecVMWithSnapshot(ctx context.Context, vm *VM, uart *serial.UART
pc, _ := vm.GetPC()
return fmt.Errorf("unexpected exit reason %d at pc=%#x\nserial:\n%s", exit.Reason, pc, serialOut.String())
}
if err := snapshot.captureIfPending(vm, fsdevs, vsock, rng, netdev); err != nil {
if err := snapshot.captureIfPending(vm, fsdevs, vsock, rng, balloon, netdev); err != nil {
return err
}
}
}

func runManagedExecVMMulti(ctx context.Context, vm *VM, uart *serial.UART8250, fsdevs []*virtio.FS, vsock *virtio.Vsock, rng *virtio.RNG, netdev *virtio.Net, serialOut *vmruntime.SerialTranscript) error {
func runManagedExecVMMulti(ctx context.Context, vm *VM, uart *serial.UART8250, fsdevs []*virtio.FS, vsock *virtio.Vsock, rng *virtio.RNG, balloon *virtio.Balloon, netdev *virtio.Net, serialOut *vmruntime.SerialTranscript) error {
runCtx, cancel := context.WithCancel(ctx)
defer cancel()
sampler := startKVMRegisterSampler(runCtx, vm)
Expand Down Expand Up @@ -292,7 +304,7 @@ func runManagedExecVMMulti(ctx context.Context, vm *VM, uart *serial.UART8250, f
return
}
exitMu.Lock()
err = handleManagedExit(vm, index, uart, fsdevs, vsock, rng, netdev, serialOut, exit)
err = handleManagedExit(vm, index, uart, fsdevs, vsock, rng, balloon, netdev, serialOut, exit)
exitMu.Unlock()
if err != nil {
reportRunErr(errCh, cancel, err)
Expand Down Expand Up @@ -388,14 +400,14 @@ func reportRunErr(errCh chan<- error, cancel context.CancelFunc, err error) {
}
}

func handleManagedExit(vm *VM, vcpuIndex int, uart *serial.UART8250, fsdevs []*virtio.FS, vsock *virtio.Vsock, rng *virtio.RNG, netdev *virtio.Net, serialOut *vmruntime.SerialTranscript, exit Exit) error {
func handleManagedExit(vm *VM, vcpuIndex int, uart *serial.UART8250, fsdevs []*virtio.FS, vsock *virtio.Vsock, rng *virtio.RNG, balloon *virtio.Balloon, netdev *virtio.Net, serialOut *vmruntime.SerialTranscript, exit Exit) error {
switch exit.Reason {
case ExitIO:
if err := handleBootIO(uart, exit.IO); err != nil {
return err
}
case ExitMMIO:
if err := handleBootMMIOForVCPU(vm, vcpuIndex, fsdevs, vsock, rng, netdev, exit.MMIO); err != nil {
if err := handleBootMMIOForVCPU(vm, vcpuIndex, fsdevs, vsock, rng, balloon, netdev, exit.MMIO); err != nil {
return err
}
case ExitHLT:
Expand Down
2 changes: 1 addition & 1 deletion internal/hv/kvm/freebsd_boot_linux_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func bootFreeBSDToCondition(ctx context.Context, kernel []byte, memoryMB uint64,
return serialOut.String(), err
}
case ExitMMIO:
if err := handleBootMMIOWithPCI(vm, 0, pci, nil, nil, nil, nil, exit.MMIO); err != nil {
if err := handleBootMMIOWithPCI(vm, 0, pci, nil, nil, nil, nil, nil, exit.MMIO); err != nil {
return serialOut.String(), fmt.Errorf("unhandled FreeBSD mmio addr=%#x len=%d write=%v: %w", exit.MMIO.Addr, exit.MMIO.Len, exit.MMIO.Write, err)
}
case ExitHLT:
Expand Down
2 changes: 1 addition & 1 deletion internal/hv/kvm/freebsd_session_linux_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func runFreeBSDManagedVM(ctx context.Context, vm *VM, uart *serial.UART8250, pci
return err
}
case ExitMMIO:
if err := handleBootMMIOWithPCI(vm, 0, pci, nil, nil, nil, nil, exit.MMIO); err != nil {
if err := handleBootMMIOWithPCI(vm, 0, pci, nil, nil, nil, nil, nil, exit.MMIO); err != nil {
return fmt.Errorf("unhandled FreeBSD mmio addr=%#x len=%d write=%v: %w", exit.MMIO.Addr, exit.MMIO.Len, exit.MMIO.Write, err)
}
case ExitHLT:
Expand Down
4 changes: 4 additions & 0 deletions internal/hv/kvm/host_linux_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ type LinuxManagedMachine struct {
Initrd []byte
FSDevices []*virtio.FS
NetDevice *virtio.Net
BalloonMB uint64
SnapshotDir string
RestoreSnapshot string
}

type LinuxManagedAttachments struct {
FSDevices []*virtio.FS
NetDevice *virtio.Net
BalloonMB uint64
SnapshotDir string
RestoreSnapshot string
}
Expand Down Expand Up @@ -76,6 +78,7 @@ func (Host) startLinux(ctx context.Context, req managedhost.StartRequest, onEven
Initrd: req.Artifact.Initrd,
FSDevices: attachments.FSDevices,
NetDevice: attachments.NetDevice,
BalloonMB: attachments.BalloonMB,
SnapshotDir: strings.TrimSpace(attachments.SnapshotDir),
RestoreSnapshot: strings.TrimSpace(attachments.RestoreSnapshot),
}, onEvent)
Expand Down Expand Up @@ -164,6 +167,7 @@ func (Host) StartLinuxManaged(ctx context.Context, machine LinuxManagedMachine,
ManagedSessionOptions{
SnapshotDir: strings.TrimSpace(machine.SnapshotDir),
RestoreSnapshot: strings.TrimSpace(machine.RestoreSnapshot),
BalloonMB: machine.BalloonMB,
},
onEvent,
)
Expand Down
2 changes: 1 addition & 1 deletion internal/hv/kvm/netbsd_boot_linux_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func bootNetBSDKernelToSerial(ctx context.Context, kernel []byte, memoryMB uint6
return serialOut.String(), err
}
case ExitMMIO:
if err := handleBootMMIOWithPCI(vm, 0, pci, nil, nil, nil, nil, exit.MMIO); err != nil {
if err := handleBootMMIOWithPCI(vm, 0, pci, nil, nil, nil, nil, nil, exit.MMIO); err != nil {
return serialOut.String(), fmt.Errorf("unhandled NetBSD mmio addr=%#x len=%d write=%v: %w", exit.MMIO.Addr, exit.MMIO.Len, exit.MMIO.Write, err)
}
case ExitHLT:
Expand Down
2 changes: 1 addition & 1 deletion internal/hv/kvm/netbsd_session_linux_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func runNetBSDManagedVM(ctx context.Context, vm *VM, uart *serial.UART8250, pci
return err
}
case ExitMMIO:
if err := handleBootMMIOWithPCI(vm, 0, pci, nil, nil, nil, nil, exit.MMIO); err != nil {
if err := handleBootMMIOWithPCI(vm, 0, pci, nil, nil, nil, nil, nil, exit.MMIO); err != nil {
return fmt.Errorf("unhandled NetBSD mmio addr=%#x len=%d write=%v: %w", exit.MMIO.Addr, exit.MMIO.Len, exit.MMIO.Write, err)
}
case ExitHLT:
Expand Down
2 changes: 1 addition & 1 deletion internal/hv/kvm/openbsd_boot_linux_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func bootOpenBSDToCondition(ctx context.Context, kernel []byte, memoryMB uint64,
return serialOut.String(), err
}
case ExitMMIO:
if err := handleBootMMIOWithPCI(vm, 0, pci, nil, nil, nil, nil, exit.MMIO); err != nil {
if err := handleBootMMIOWithPCI(vm, 0, pci, nil, nil, nil, nil, nil, exit.MMIO); err != nil {
return serialOut.String(), fmt.Errorf("unhandled OpenBSD mmio addr=%#x len=%d write=%v: %w", exit.MMIO.Addr, exit.MMIO.Len, exit.MMIO.Write, err)
}
case ExitHLT:
Expand Down
2 changes: 1 addition & 1 deletion internal/hv/kvm/openbsd_session_linux_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func runOpenBSDManagedVM(ctx context.Context, vm *VM, uart *serial.UART8250, pci
return err
}
case ExitMMIO:
if err := handleBootMMIOWithPCI(vm, 0, pci, nil, nil, nil, nil, exit.MMIO); err != nil {
if err := handleBootMMIOWithPCI(vm, 0, pci, nil, nil, nil, nil, nil, exit.MMIO); err != nil {
return fmt.Errorf("unhandled OpenBSD mmio addr=%#x len=%d write=%v: %w", exit.MMIO.Addr, exit.MMIO.Len, exit.MMIO.Write, err)
}
case ExitHLT:
Expand Down
4 changes: 2 additions & 2 deletions internal/hv/kvm/pci_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ func handleBootIOWithPCI(uartIO func(IOExit) error, pci *PCIBus, ioExit IOExit)
return uartIO(ioExit)
}

func handleBootMMIOWithPCI(vm *VM, vcpuIndex int, pci *PCIBus, fsdevs []*virtio.FS, vsock *virtio.Vsock, rng *virtio.RNG, netdev *virtio.Net, mmio MMIOExit) error {
func handleBootMMIOWithPCI(vm *VM, vcpuIndex int, pci *PCIBus, fsdevs []*virtio.FS, vsock *virtio.Vsock, rng *virtio.RNG, balloon *virtio.Balloon, netdev *virtio.Net, mmio MMIOExit) error {
if handled, value, err := pci.HandleMMIO(mmio); handled || err != nil {
if err != nil {
return err
Expand All @@ -352,7 +352,7 @@ func handleBootMMIOWithPCI(vm *VM, vcpuIndex int, pci *PCIBus, fsdevs []*virtio.
}
return nil
}
return handleBootMMIOForVCPU(vm, vcpuIndex, fsdevs, vsock, rng, netdev, mmio)
return handleBootMMIOForVCPU(vm, vcpuIndex, fsdevs, vsock, rng, balloon, netdev, mmio)
}

func readLittleValue(data []byte) uint64 {
Expand Down
15 changes: 13 additions & 2 deletions internal/hv/kvm/session_linux_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type ManagedSession struct {
type ManagedSessionOptions struct {
SnapshotDir string
RestoreSnapshot string
BalloonMB uint64
}

func StartManagedSession(ctx context.Context, kernel []byte, initrd []byte, memoryMB uint64, cpus int, dmesg bool, fsdevs []*virtio.FS, onEvent func(client.BootEvent) error) (*ManagedSession, error) {
Expand All @@ -55,7 +56,7 @@ func StartManagedSessionWithNetOptions(ctx context.Context, kernel []byte, initr
}
}
if snapshotPath := strings.TrimSpace(opts.RestoreSnapshot); snapshotPath != "" {
return StartManagedSessionFromSnapshot(ctx, snapshotPath, memoryMB, dmesg, fsdevs, netdev, onEvent)
return StartManagedSessionFromSnapshot(ctx, snapshotPath, memoryMB, opts.BalloonMB, dmesg, fsdevs, netdev, onEvent)
}
if err := emitManagedBootStatus(onEvent, "starting VM"); err != nil {
return nil, err
Expand All @@ -68,6 +69,14 @@ func StartManagedSessionWithNetOptions(ctx context.Context, kernel []byte, initr

vsock := virtio.NewVsock(amd64vm.VsockBase, amd64vm.VsockSize, amd64vm.VsockIRQ, vmruntime.GuestCID, backend)
rng := virtio.NewRNG(amd64vm.RNGBase, amd64vm.RNGSize, amd64vm.RNGIRQ)
balloon := virtio.NewBalloon(amd64vm.BalloonBase, amd64vm.BalloonSize, amd64vm.BalloonIRQ)
if targetPages := balloonTargetPages(opts.BalloonMB); targetPages != 0 {
if err := balloon.SetTargetPages(targetPages); err != nil {
_ = listener.Close()
vsock.Close()
return nil, fmt.Errorf("set balloon target: %w", err)
}
}
connCh := make(chan virtio.VsockConn, 1)
acceptErrCh := make(chan error, 1)
controlTranscript := vmruntime.NewSerialTranscript()
Expand Down Expand Up @@ -112,13 +121,15 @@ func StartManagedSessionWithNetOptions(ctx context.Context, kernel []byte, initr
}
vsock.Attach(vm, vm)
rng.Attach(vm, vm)
balloon.Attach(vm, vm)
if netdev != nil {
netdev.Attach(vm, vm)
}

extraCmdline := amd64vm.VirtioFSCommandLineArgs(fsdevs)
extraCmdline = append(extraCmdline, amd64vm.VirtioMMIODeviceArg(vsock.Base, vsock.IRQ))
extraCmdline = append(extraCmdline, amd64vm.VirtioMMIODeviceArg(rng.Base, rng.IRQ))
extraCmdline = append(extraCmdline, amd64vm.VirtioMMIODeviceArg(balloon.Base, balloon.IRQ))
if netdev != nil {
extraCmdline = append(extraCmdline, amd64vm.VirtioMMIODeviceArg(netdev.Base, netdev.IRQ))
}
Expand Down Expand Up @@ -151,7 +162,7 @@ func StartManagedSessionWithNetOptions(ctx context.Context, kernel []byte, initr
runCtx, cancel := context.WithCancel(context.Background())
done := newSessionDone()
go func() {
err := runManagedExecVMWithSnapshot(runCtx, vm, uart, fsdevs, vsock, rng, netdev, serialOut, snapshot)
err := runManagedExecVMWithSnapshot(runCtx, vm, uart, fsdevs, vsock, rng, balloon, netdev, serialOut, snapshot)
closeVMWithFS(vm, fsdevs)
done.finish(err)
}()
Expand Down
Loading
Loading