Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/driver/vz/vm_darwin.go
2611 views
1
//go:build darwin && !no_vz
2
3
// SPDX-FileCopyrightText: Copyright The Lima Authors
4
// SPDX-License-Identifier: Apache-2.0
5
6
package vz
7
8
import (
9
"context"
10
"errors"
11
"fmt"
12
"io/fs"
13
"net"
14
"os"
15
"path/filepath"
16
"runtime"
17
"slices"
18
"strconv"
19
"sync"
20
"syscall"
21
22
"github.com/Code-Hex/vz/v3"
23
"github.com/coreos/go-semver/semver"
24
"github.com/docker/go-units"
25
"github.com/lima-vm/go-qcow2reader"
26
"github.com/lima-vm/go-qcow2reader/image"
27
"github.com/lima-vm/go-qcow2reader/image/asif"
28
"github.com/lima-vm/go-qcow2reader/image/raw"
29
"github.com/sirupsen/logrus"
30
31
"github.com/lima-vm/lima/v2/pkg/hostagent/events"
32
"github.com/lima-vm/lima/v2/pkg/imgutil/proxyimgutil"
33
"github.com/lima-vm/lima/v2/pkg/iso9660util"
34
"github.com/lima-vm/lima/v2/pkg/limatype"
35
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
36
"github.com/lima-vm/lima/v2/pkg/limayaml"
37
"github.com/lima-vm/lima/v2/pkg/networks"
38
"github.com/lima-vm/lima/v2/pkg/networks/usernet"
39
"github.com/lima-vm/lima/v2/pkg/osutil"
40
"github.com/lima-vm/lima/v2/pkg/store"
41
)
42
43
// diskImageCachingMode is set to DiskImageCachingModeCached so as to avoid disk corruption on ARM:
44
// - https://github.com/utmapp/UTM/issues/4840#issuecomment-1824340975
45
// - https://github.com/utmapp/UTM/issues/4840#issuecomment-1824542732
46
//
47
// Eventually we may bring this back to DiskImageCachingModeAutomatic when the corruption issue is properly fixed.
48
const diskImageCachingMode = vz.DiskImageCachingModeCached
49
50
type virtualMachineWrapper struct {
51
*vz.VirtualMachine
52
mu sync.Mutex
53
stopped bool
54
}
55
56
// Hold all *os.File created via socketpair() so that they won't get garbage collected. f.FD() gets invalid if f gets garbage collected.
57
var vmNetworkFiles = make([]*os.File, 1)
58
59
func startVM(ctx context.Context, inst *limatype.Instance, sshLocalPort int, onVsockEvent func(*events.VsockEvent)) (vm *virtualMachineWrapper, waitSSHLocalPortAccessible <-chan any, errCh chan error, err error) {
60
usernetClient, stopUsernet, err := startUsernet(ctx, inst)
61
if err != nil {
62
return nil, nil, nil, err
63
}
64
65
machine, err := createVM(ctx, inst)
66
if err != nil {
67
return nil, nil, nil, err
68
}
69
70
err = machine.Start()
71
if err != nil {
72
return nil, nil, nil, err
73
}
74
75
wrapper := &virtualMachineWrapper{VirtualMachine: machine, stopped: false}
76
notifySSHLocalPortAccessible := make(chan any)
77
sendErrCh := make(chan error)
78
79
go func() {
80
// Handle errors via errCh and handle stop vm during context close
81
defer func() {
82
for i := range vmNetworkFiles {
83
vmNetworkFiles[i].Close()
84
}
85
}()
86
for {
87
select {
88
case <-ctx.Done():
89
logrus.Info("Context closed, stopping vm")
90
if machine.CanStop() {
91
_, err := machine.RequestStop()
92
logrus.Errorf("Error while stopping the VM %q", err)
93
}
94
case newState := <-machine.StateChangedNotify():
95
switch newState {
96
case vz.VirtualMachineStateRunning:
97
pidFile := filepath.Join(inst.Dir, filenames.PIDFile(*inst.Config.VMType))
98
if _, err := os.Stat(pidFile); !errors.Is(err, os.ErrNotExist) {
99
logrus.Errorf("pidfile %q already exists", pidFile)
100
sendErrCh <- err
101
}
102
if err := os.WriteFile(pidFile, []byte(strconv.Itoa(os.Getpid())+"\n"), 0o644); err != nil {
103
logrus.Errorf("error writing to pid fil %q", pidFile)
104
sendErrCh <- err
105
}
106
logrus.Info("[VZ] - vm state change: running")
107
108
go func() {
109
defer close(notifySSHLocalPortAccessible)
110
usernetSSHLocalPort := sshLocalPort
111
useSSHOverVsock := true
112
if inst.Config.SSH.OverVsock != nil {
113
useSSHOverVsock = *inst.Config.SSH.OverVsock
114
}
115
if !useSSHOverVsock {
116
logrus.Info("ssh.overVsock is false, skipping detection of SSH server on vsock port")
117
if onVsockEvent != nil {
118
onVsockEvent(&events.VsockEvent{
119
Type: events.VsockEventSkipped,
120
Reason: "ssh.overVsock is false",
121
})
122
}
123
} else if err := usernetClient.WaitOpeningSSHPort(ctx, inst); err == nil {
124
hostAddress := net.JoinHostPort(inst.SSHAddress, strconv.Itoa(usernetSSHLocalPort))
125
if err := wrapper.startVsockForwarder(ctx, 22, hostAddress); err == nil {
126
logrus.Infof("Detected SSH server is listening on the vsock port; changed %s to proxy for the vsock port", hostAddress)
127
if onVsockEvent != nil {
128
onVsockEvent(&events.VsockEvent{
129
Type: events.VsockEventStarted,
130
HostAddr: hostAddress,
131
VsockPort: 22,
132
})
133
}
134
usernetSSHLocalPort = 0 // disable gvisor ssh port forwarding
135
} else {
136
logrus.WithError(err).WithField("hostAddress", hostAddress).
137
Debugf("Failed to start vsock forwarder (systemd is older than v256?)")
138
logrus.Info("SSH server does not seem to be running on vsock port, using usernet forwarder")
139
if onVsockEvent != nil {
140
onVsockEvent(&events.VsockEvent{
141
Type: events.VsockEventFailed,
142
Reason: "SSH server does not seem to be running on vsock port",
143
})
144
}
145
}
146
} else {
147
logrus.WithError(err).Warn("Failed to wait for the guest SSH server to become available, falling back to usernet forwarder")
148
if onVsockEvent != nil {
149
onVsockEvent(&events.VsockEvent{
150
Type: events.VsockEventFailed,
151
Reason: "Failed to wait for guest SSH server",
152
})
153
}
154
}
155
err := usernetClient.ConfigureDriver(ctx, inst, usernetSSHLocalPort)
156
if err != nil {
157
sendErrCh <- err
158
}
159
}()
160
case vz.VirtualMachineStateStopped:
161
logrus.Info("[VZ] - vm state change: stopped")
162
wrapper.mu.Lock()
163
wrapper.stopped = true
164
wrapper.mu.Unlock()
165
_ = usernetClient.UnExposeSSH(inst.SSHLocalPort)
166
if stopUsernet != nil {
167
stopUsernet()
168
}
169
sendErrCh <- errors.New("vz driver state stopped")
170
default:
171
logrus.Debugf("[VZ] - vm state change: %q", newState)
172
}
173
}
174
}
175
}()
176
return wrapper, notifySSHLocalPortAccessible, sendErrCh, err
177
}
178
179
func startUsernet(ctx context.Context, inst *limatype.Instance) (*usernet.Client, context.CancelFunc, error) {
180
if firstUsernetIndex := limayaml.FirstUsernetIndex(inst.Config); firstUsernetIndex != -1 {
181
nwName := inst.Config.Networks[firstUsernetIndex].Lima
182
return usernet.NewClientByName(nwName), nil, nil
183
}
184
// Start a in-process gvisor-tap-vsock
185
endpointSock, err := usernet.SockWithDirectory(inst.Dir, "", usernet.EndpointSock)
186
if err != nil {
187
return nil, nil, err
188
}
189
vzSock, err := usernet.SockWithDirectory(inst.Dir, "", usernet.FDSock)
190
if err != nil {
191
return nil, nil, err
192
}
193
os.RemoveAll(endpointSock)
194
os.RemoveAll(vzSock)
195
ctx, cancel := context.WithCancel(ctx)
196
err = usernet.StartGVisorNetstack(ctx, &usernet.GVisorNetstackOpts{
197
MTU: 1500,
198
Endpoint: endpointSock,
199
FdSocket: vzSock,
200
Async: true,
201
DefaultLeases: map[string]string{
202
networks.SlirpIPAddress: limayaml.MACAddress(inst.Dir),
203
},
204
Subnet: networks.SlirpNetwork,
205
})
206
if err != nil {
207
defer cancel()
208
return nil, nil, err
209
}
210
subnetIP, _, err := net.ParseCIDR(networks.SlirpNetwork)
211
return usernet.NewClient(endpointSock, subnetIP), cancel, err
212
}
213
214
func createVM(ctx context.Context, inst *limatype.Instance) (*vz.VirtualMachine, error) {
215
vmConfig, err := createInitialConfig(inst)
216
if err != nil {
217
return nil, err
218
}
219
220
if err = attachPlatformConfig(inst, vmConfig); err != nil {
221
return nil, err
222
}
223
224
if err = attachSerialPort(inst, vmConfig); err != nil {
225
return nil, err
226
}
227
228
if err = attachNetwork(ctx, inst, vmConfig); err != nil {
229
return nil, err
230
}
231
232
if err = attachDisks(ctx, inst, vmConfig); err != nil {
233
return nil, err
234
}
235
236
if err = attachDisplay(inst, vmConfig); err != nil {
237
return nil, err
238
}
239
240
if err = attachFolderMounts(inst, vmConfig); err != nil {
241
return nil, err
242
}
243
244
if err = attachAudio(inst, vmConfig); err != nil {
245
return nil, err
246
}
247
248
if err = attachOtherDevices(inst, vmConfig); err != nil {
249
return nil, err
250
}
251
252
validated, err := vmConfig.Validate()
253
if !validated || err != nil {
254
return nil, err
255
}
256
257
return vz.NewVirtualMachine(vmConfig)
258
}
259
260
func createInitialConfig(inst *limatype.Instance) (*vz.VirtualMachineConfiguration, error) {
261
bootLoader, err := bootLoader(inst)
262
if err != nil {
263
return nil, err
264
}
265
266
bytes, err := units.RAMInBytes(*inst.Config.Memory)
267
if err != nil {
268
return nil, err
269
}
270
271
vmConfig, err := vz.NewVirtualMachineConfiguration(
272
bootLoader,
273
uint(*inst.Config.CPUs),
274
uint64(bytes),
275
)
276
if err != nil {
277
return nil, err
278
}
279
return vmConfig, nil
280
}
281
282
func attachPlatformConfig(inst *limatype.Instance, vmConfig *vz.VirtualMachineConfiguration) error {
283
machineIdentifier, err := getMachineIdentifier(inst)
284
if err != nil {
285
return err
286
}
287
288
platformConfig, err := vz.NewGenericPlatformConfiguration(vz.WithGenericMachineIdentifier(machineIdentifier))
289
if err != nil {
290
return err
291
}
292
293
// nested virt
294
if *inst.Config.NestedVirtualization {
295
macOSProductVersion, err := osutil.ProductVersion()
296
if err != nil {
297
return fmt.Errorf("failed to get macOS product version: %w", err)
298
}
299
300
if macOSProductVersion.LessThan(*semver.New("15.0.0")) {
301
return errors.New("nested virtualization requires macOS 15 or newer")
302
}
303
304
if !vz.IsNestedVirtualizationSupported() {
305
return errors.New("nested virtualization is not supported on this device")
306
}
307
308
if err := platformConfig.SetNestedVirtualizationEnabled(true); err != nil {
309
return fmt.Errorf("cannot enable nested virtualization: %w", err)
310
}
311
}
312
313
vmConfig.SetPlatformVirtualMachineConfiguration(platformConfig)
314
return nil
315
}
316
317
func attachSerialPort(inst *limatype.Instance, config *vz.VirtualMachineConfiguration) error {
318
path := filepath.Join(inst.Dir, filenames.SerialVirtioLog)
319
serialPortAttachment, err := vz.NewFileSerialPortAttachment(path, false)
320
if err != nil {
321
return err
322
}
323
consoleConfig, err := vz.NewVirtioConsoleDeviceSerialPortConfiguration(serialPortAttachment)
324
config.SetSerialPortsVirtualMachineConfiguration([]*vz.VirtioConsoleDeviceSerialPortConfiguration{
325
consoleConfig,
326
})
327
return err
328
}
329
330
func newVirtioFileNetworkDeviceConfiguration(file *os.File, macStr string) (*vz.VirtioNetworkDeviceConfiguration, error) {
331
fileAttachment, err := vz.NewFileHandleNetworkDeviceAttachment(file)
332
if err != nil {
333
return nil, err
334
}
335
return newVirtioNetworkDeviceConfiguration(fileAttachment, macStr)
336
}
337
338
func newVirtioNetworkDeviceConfiguration(attachment vz.NetworkDeviceAttachment, macStr string) (*vz.VirtioNetworkDeviceConfiguration, error) {
339
networkConfig, err := vz.NewVirtioNetworkDeviceConfiguration(attachment)
340
if err != nil {
341
return nil, err
342
}
343
mac, err := net.ParseMAC(macStr)
344
if err != nil {
345
return nil, err
346
}
347
address, err := vz.NewMACAddress(mac)
348
if err != nil {
349
return nil, err
350
}
351
networkConfig.SetMACAddress(address)
352
return networkConfig, nil
353
}
354
355
func attachNetwork(ctx context.Context, inst *limatype.Instance, vmConfig *vz.VirtualMachineConfiguration) error {
356
var configurations []*vz.VirtioNetworkDeviceConfiguration
357
358
// Configure default usernetwork with limayaml.MACAddress(inst.Dir) for eth0 interface
359
firstUsernetIndex := limayaml.FirstUsernetIndex(inst.Config)
360
if firstUsernetIndex == -1 {
361
// slirp network using gvisor netstack
362
vzSock, err := usernet.SockWithDirectory(inst.Dir, "", usernet.FDSock)
363
if err != nil {
364
return err
365
}
366
networkConn, err := PassFDToUnix(vzSock)
367
if err != nil {
368
return err
369
}
370
networkConfig, err := newVirtioFileNetworkDeviceConfiguration(networkConn, limayaml.MACAddress(inst.Dir))
371
if err != nil {
372
return err
373
}
374
configurations = append(configurations, networkConfig)
375
} else {
376
vzSock, err := usernet.Sock(inst.Config.Networks[firstUsernetIndex].Lima, usernet.FDSock)
377
if err != nil {
378
return err
379
}
380
networkConn, err := PassFDToUnix(vzSock)
381
if err != nil {
382
return err
383
}
384
networkConfig, err := newVirtioFileNetworkDeviceConfiguration(networkConn, limayaml.MACAddress(inst.Dir))
385
if err != nil {
386
return err
387
}
388
configurations = append(configurations, networkConfig)
389
}
390
391
for i, nw := range inst.Networks {
392
if nw.VZNAT != nil && *nw.VZNAT {
393
attachment, err := vz.NewNATNetworkDeviceAttachment()
394
if err != nil {
395
return err
396
}
397
networkConfig, err := newVirtioNetworkDeviceConfiguration(attachment, nw.MACAddress)
398
if err != nil {
399
return err
400
}
401
configurations = append(configurations, networkConfig)
402
} else if nw.Lima != "" {
403
nwCfg, err := networks.LoadConfig()
404
if err != nil {
405
return err
406
}
407
isUsernet, err := nwCfg.Usernet(nw.Lima)
408
if err != nil {
409
return err
410
}
411
if isUsernet {
412
if i == firstUsernetIndex {
413
continue
414
}
415
vzSock, err := usernet.Sock(nw.Lima, usernet.FDSock)
416
if err != nil {
417
return err
418
}
419
clientFile, err := PassFDToUnix(vzSock)
420
if err != nil {
421
return err
422
}
423
networkConfig, err := newVirtioFileNetworkDeviceConfiguration(clientFile, nw.MACAddress)
424
if err != nil {
425
return err
426
}
427
configurations = append(configurations, networkConfig)
428
} else {
429
if runtime.GOOS != "darwin" {
430
return fmt.Errorf("networks.yaml '%s' configuration is only supported on macOS right now", nw.Lima)
431
}
432
socketVMNetOk, err := nwCfg.IsDaemonInstalled(networks.SocketVMNet)
433
if err != nil {
434
return err
435
}
436
if socketVMNetOk {
437
logrus.Debugf("Using socketVMNet (%q)", nwCfg.Paths.SocketVMNet)
438
sock, err := networks.Sock(nw.Lima)
439
if err != nil {
440
return err
441
}
442
443
clientFile, err := DialQemu(ctx, sock)
444
if err != nil {
445
return err
446
}
447
networkConfig, err := newVirtioFileNetworkDeviceConfiguration(clientFile, nw.MACAddress)
448
if err != nil {
449
return err
450
}
451
configurations = append(configurations, networkConfig)
452
}
453
}
454
} else if nw.Socket != "" {
455
clientFile, err := DialQemu(ctx, nw.Socket)
456
if err != nil {
457
return err
458
}
459
networkConfig, err := newVirtioFileNetworkDeviceConfiguration(clientFile, nw.MACAddress)
460
if err != nil {
461
return err
462
}
463
configurations = append(configurations, networkConfig)
464
}
465
}
466
vmConfig.SetNetworkDevicesVirtualMachineConfiguration(configurations)
467
return nil
468
}
469
470
func validateDiskFormat(diskPath string) error {
471
f, err := os.Open(diskPath)
472
if err != nil {
473
return err
474
}
475
defer f.Close()
476
img, err := qcow2reader.Open(f)
477
if err != nil {
478
return fmt.Errorf("failed to detect the format of %q: %w", diskPath, err)
479
}
480
supportedDiskTypes := []image.Type{raw.Type, asif.Type}
481
if t := img.Type(); !slices.Contains(supportedDiskTypes, t) {
482
return fmt.Errorf("expected the format of %q to be one of %v, got %q", diskPath, supportedDiskTypes, t)
483
}
484
// TODO: ensure that the disk is formatted with GPT or ISO9660
485
return nil
486
}
487
488
func attachDisks(ctx context.Context, inst *limatype.Instance, vmConfig *vz.VirtualMachineConfiguration) error {
489
baseDiskPath := filepath.Join(inst.Dir, filenames.BaseDisk)
490
diffDiskPath := filepath.Join(inst.Dir, filenames.DiffDisk)
491
ciDataPath := filepath.Join(inst.Dir, filenames.CIDataISO)
492
isBaseDiskCDROM, err := iso9660util.IsISO9660(baseDiskPath)
493
if err != nil {
494
return err
495
}
496
var configurations []vz.StorageDeviceConfiguration
497
498
if isBaseDiskCDROM {
499
if err = validateDiskFormat(baseDiskPath); err != nil {
500
return err
501
}
502
baseDiskAttachment, err := vz.NewDiskImageStorageDeviceAttachment(baseDiskPath, true)
503
if err != nil {
504
return err
505
}
506
baseDisk, err := vz.NewUSBMassStorageDeviceConfiguration(baseDiskAttachment)
507
if err != nil {
508
return err
509
}
510
configurations = append(configurations, baseDisk)
511
}
512
if err = validateDiskFormat(diffDiskPath); err != nil {
513
return err
514
}
515
diffDiskAttachment, err := vz.NewDiskImageStorageDeviceAttachmentWithCacheAndSync(diffDiskPath, false, diskImageCachingMode, vz.DiskImageSynchronizationModeFsync)
516
if err != nil {
517
return err
518
}
519
diffDisk, err := vz.NewVirtioBlockDeviceConfiguration(diffDiskAttachment)
520
if err != nil {
521
return err
522
}
523
configurations = append(configurations, diffDisk)
524
525
diskUtil := proxyimgutil.NewDiskUtil(ctx)
526
527
for _, d := range inst.Config.AdditionalDisks {
528
diskName := d.Name
529
disk, err := store.InspectDisk(diskName, d.FSType)
530
if err != nil {
531
return fmt.Errorf("failed to run load disk %q: %w", diskName, err)
532
}
533
534
if disk.Instance != "" {
535
return fmt.Errorf("failed to run attach disk %q, in use by instance %q", diskName, disk.Instance)
536
}
537
logrus.Infof("Mounting disk %q on %q", diskName, disk.MountPoint)
538
err = disk.Lock(inst.Dir)
539
if err != nil {
540
return fmt.Errorf("failed to run lock disk %q: %w", diskName, err)
541
}
542
extraDiskPath := filepath.Join(disk.Dir, filenames.DataDisk)
543
// ConvertToRaw is a NOP if no conversion is needed
544
logrus.Debugf("Converting extra disk %q to a raw disk (if it is not a raw)", extraDiskPath)
545
546
if err = diskUtil.Convert(ctx, raw.Type, extraDiskPath, extraDiskPath, nil, true); err != nil {
547
return fmt.Errorf("failed to convert extra disk %q to a raw disk: %w", extraDiskPath, err)
548
}
549
extraDiskPathAttachment, err := vz.NewDiskImageStorageDeviceAttachmentWithCacheAndSync(extraDiskPath, false, diskImageCachingMode, vz.DiskImageSynchronizationModeFsync)
550
if err != nil {
551
return fmt.Errorf("failed to create disk attachment for extra disk %q: %w", extraDiskPath, err)
552
}
553
extraDisk, err := vz.NewVirtioBlockDeviceConfiguration(extraDiskPathAttachment)
554
if err != nil {
555
return fmt.Errorf("failed to create new virtio block device config for extra disk %q: %w", extraDiskPath, err)
556
}
557
configurations = append(configurations, extraDisk)
558
}
559
560
if err = validateDiskFormat(ciDataPath); err != nil {
561
return err
562
}
563
ciDataAttachment, err := vz.NewDiskImageStorageDeviceAttachment(ciDataPath, true)
564
if err != nil {
565
return err
566
}
567
ciData, err := vz.NewVirtioBlockDeviceConfiguration(ciDataAttachment)
568
if err != nil {
569
return err
570
}
571
configurations = append(configurations, ciData)
572
573
vmConfig.SetStorageDevicesVirtualMachineConfiguration(configurations)
574
return nil
575
}
576
577
func attachDisplay(inst *limatype.Instance, vmConfig *vz.VirtualMachineConfiguration) error {
578
switch *inst.Config.Video.Display {
579
case "vz", "default":
580
graphicsDeviceConfiguration, err := vz.NewVirtioGraphicsDeviceConfiguration()
581
if err != nil {
582
return err
583
}
584
scanoutConfiguration, err := vz.NewVirtioGraphicsScanoutConfiguration(1920, 1200)
585
if err != nil {
586
return err
587
}
588
graphicsDeviceConfiguration.SetScanouts(scanoutConfiguration)
589
590
vmConfig.SetGraphicsDevicesVirtualMachineConfiguration([]vz.GraphicsDeviceConfiguration{
591
graphicsDeviceConfiguration,
592
})
593
return nil
594
case "none":
595
return nil
596
default:
597
return fmt.Errorf("unexpected video display %q", *inst.Config.Video.Display)
598
}
599
}
600
601
func attachFolderMounts(inst *limatype.Instance, vmConfig *vz.VirtualMachineConfiguration) error {
602
var mounts []vz.DirectorySharingDeviceConfiguration
603
if *inst.Config.MountType == limatype.VIRTIOFS {
604
for _, mount := range inst.Config.Mounts {
605
if _, err := os.Stat(mount.Location); errors.Is(err, os.ErrNotExist) {
606
err := os.MkdirAll(mount.Location, 0o750)
607
if err != nil {
608
return err
609
}
610
}
611
612
directory, err := vz.NewSharedDirectory(mount.Location, !*mount.Writable)
613
if err != nil {
614
return err
615
}
616
share, err := vz.NewSingleDirectoryShare(directory)
617
if err != nil {
618
return err
619
}
620
621
tag := limayaml.MountTag(mount.Location, *mount.MountPoint)
622
config, err := vz.NewVirtioFileSystemDeviceConfiguration(tag)
623
if err != nil {
624
return err
625
}
626
config.SetDirectoryShare(share)
627
mounts = append(mounts, config)
628
}
629
}
630
631
var vzOpts limatype.VZOpts
632
if err := limayaml.Convert(inst.Config.VMOpts[limatype.VZ], &vzOpts, "vmOpts.vz"); err != nil {
633
logrus.WithError(err).Warnf("Couldn't convert %q", inst.Config.VMOpts[limatype.VZ])
634
}
635
636
if vzOpts.Rosetta.Enabled != nil && *vzOpts.Rosetta.Enabled {
637
logrus.Info("Setting up Rosetta share")
638
directorySharingDeviceConfig, err := createRosettaDirectoryShareConfiguration()
639
if err != nil {
640
logrus.Warnf("Unable to configure Rosetta: %s", err)
641
} else {
642
mounts = append(mounts, directorySharingDeviceConfig)
643
}
644
}
645
646
if len(mounts) > 0 {
647
vmConfig.SetDirectorySharingDevicesVirtualMachineConfiguration(mounts)
648
}
649
return nil
650
}
651
652
func attachAudio(inst *limatype.Instance, config *vz.VirtualMachineConfiguration) error {
653
switch *inst.Config.Audio.Device {
654
case "vz", "default":
655
outputStream, err := vz.NewVirtioSoundDeviceHostOutputStreamConfiguration()
656
if err != nil {
657
return err
658
}
659
soundDeviceConfiguration, err := vz.NewVirtioSoundDeviceConfiguration()
660
if err != nil {
661
return err
662
}
663
soundDeviceConfiguration.SetStreams(outputStream)
664
config.SetAudioDevicesVirtualMachineConfiguration([]vz.AudioDeviceConfiguration{
665
soundDeviceConfiguration,
666
})
667
return nil
668
case "", "none":
669
return nil
670
default:
671
return fmt.Errorf("unexpected audio device %q", *inst.Config.Audio.Device)
672
}
673
}
674
675
func attachOtherDevices(_ *limatype.Instance, vmConfig *vz.VirtualMachineConfiguration) error {
676
entropyConfig, err := vz.NewVirtioEntropyDeviceConfiguration()
677
if err != nil {
678
return err
679
}
680
vmConfig.SetEntropyDevicesVirtualMachineConfiguration([]*vz.VirtioEntropyDeviceConfiguration{
681
entropyConfig,
682
})
683
684
configuration, err := vz.NewVirtioTraditionalMemoryBalloonDeviceConfiguration()
685
if err != nil {
686
return err
687
}
688
vmConfig.SetMemoryBalloonDevicesVirtualMachineConfiguration([]vz.MemoryBalloonDeviceConfiguration{
689
configuration,
690
})
691
692
deviceConfiguration, err := vz.NewVirtioSocketDeviceConfiguration()
693
vmConfig.SetSocketDevicesVirtualMachineConfiguration([]vz.SocketDeviceConfiguration{
694
deviceConfiguration,
695
})
696
if err != nil {
697
return err
698
}
699
700
// Set audio device
701
inputAudioDeviceConfig, err := vz.NewVirtioSoundDeviceConfiguration()
702
if err != nil {
703
return err
704
}
705
inputStream, err := vz.NewVirtioSoundDeviceHostInputStreamConfiguration()
706
if err != nil {
707
return err
708
}
709
inputAudioDeviceConfig.SetStreams(
710
inputStream,
711
)
712
713
outputAudioDeviceConfig, err := vz.NewVirtioSoundDeviceConfiguration()
714
if err != nil {
715
return err
716
}
717
outputStream, err := vz.NewVirtioSoundDeviceHostOutputStreamConfiguration()
718
if err != nil {
719
return err
720
}
721
outputAudioDeviceConfig.SetStreams(
722
outputStream,
723
)
724
vmConfig.SetAudioDevicesVirtualMachineConfiguration([]vz.AudioDeviceConfiguration{
725
inputAudioDeviceConfig,
726
outputAudioDeviceConfig,
727
})
728
729
// Set pointing device
730
pointingDeviceConfig, err := vz.NewUSBScreenCoordinatePointingDeviceConfiguration()
731
if err != nil {
732
return err
733
}
734
vmConfig.SetPointingDevicesVirtualMachineConfiguration([]vz.PointingDeviceConfiguration{
735
pointingDeviceConfig,
736
})
737
738
// Set keyboard device
739
keyboardDeviceConfig, err := vz.NewUSBKeyboardConfiguration()
740
if err != nil {
741
return err
742
}
743
vmConfig.SetKeyboardsVirtualMachineConfiguration([]vz.KeyboardConfiguration{
744
keyboardDeviceConfig,
745
})
746
return nil
747
}
748
749
func getMachineIdentifier(inst *limatype.Instance) (*vz.GenericMachineIdentifier, error) {
750
identifier := filepath.Join(inst.Dir, filenames.VzIdentifier)
751
// Empty VzIdentifier can be created on cloning an instance.
752
if st, err := os.Stat(identifier); err != nil || (st != nil && st.Size() == 0) {
753
if err != nil && !errors.Is(err, fs.ErrNotExist) {
754
return nil, err
755
}
756
machineIdentifier, err := vz.NewGenericMachineIdentifier()
757
if err != nil {
758
return nil, err
759
}
760
err = os.WriteFile(identifier, machineIdentifier.DataRepresentation(), 0o666)
761
if err != nil {
762
return nil, err
763
}
764
return machineIdentifier, nil
765
}
766
return vz.NewGenericMachineIdentifierWithDataPath(identifier)
767
}
768
769
func bootLoader(inst *limatype.Instance) (vz.BootLoader, error) {
770
linuxBootLoader, err := linuxBootLoader(inst)
771
if linuxBootLoader != nil {
772
return linuxBootLoader, nil
773
} else if !errors.Is(err, os.ErrNotExist) {
774
return nil, err
775
}
776
777
efiVariableStore, err := getEFI(inst)
778
if err != nil {
779
return nil, err
780
}
781
logrus.Debugf("Using EFI Boot Loader")
782
return vz.NewEFIBootLoader(vz.WithEFIVariableStore(efiVariableStore))
783
}
784
785
func linuxBootLoader(inst *limatype.Instance) (*vz.LinuxBootLoader, error) {
786
kernel := filepath.Join(inst.Dir, filenames.Kernel)
787
kernelCmdline := filepath.Join(inst.Dir, filenames.KernelCmdline)
788
initrd := filepath.Join(inst.Dir, filenames.Initrd)
789
if _, err := os.Stat(kernel); err != nil {
790
if errors.Is(err, os.ErrNotExist) {
791
logrus.Debugf("Kernel file %q not found", kernel)
792
} else {
793
logrus.WithError(err).Debugf("Error while checking kernel file %q", kernel)
794
}
795
return nil, err
796
}
797
var opt []vz.LinuxBootLoaderOption
798
if b, err := os.ReadFile(kernelCmdline); err == nil {
799
logrus.Debugf("Using kernel command line %q", string(b))
800
opt = append(opt, vz.WithCommandLine(string(b)))
801
}
802
if _, err := os.Stat(initrd); err == nil {
803
logrus.Debugf("Using initrd %q", initrd)
804
opt = append(opt, vz.WithInitrd(initrd))
805
}
806
logrus.Debugf("Using Linux Boot Loader with kernel %q", kernel)
807
return vz.NewLinuxBootLoader(kernel, opt...)
808
}
809
810
func getEFI(inst *limatype.Instance) (*vz.EFIVariableStore, error) {
811
efi := filepath.Join(inst.Dir, filenames.VzEfi)
812
if _, err := os.Stat(efi); os.IsNotExist(err) {
813
return vz.NewEFIVariableStore(efi, vz.WithCreatingEFIVariableStore())
814
}
815
return vz.NewEFIVariableStore(efi)
816
}
817
818
func createSockPair() (server, client *os.File, _ error) {
819
pairs, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_DGRAM, 0)
820
if err != nil {
821
return nil, nil, err
822
}
823
serverFD := pairs[0]
824
clientFD := pairs[1]
825
826
if err = syscall.SetsockoptInt(serverFD, syscall.SOL_SOCKET, syscall.SO_SNDBUF, 1*1024*1024); err != nil {
827
return nil, nil, err
828
}
829
if err = syscall.SetsockoptInt(serverFD, syscall.SOL_SOCKET, syscall.SO_RCVBUF, 4*1024*1024); err != nil {
830
return nil, nil, err
831
}
832
if err = syscall.SetsockoptInt(clientFD, syscall.SOL_SOCKET, syscall.SO_SNDBUF, 1*1024*1024); err != nil {
833
return nil, nil, err
834
}
835
if err = syscall.SetsockoptInt(clientFD, syscall.SOL_SOCKET, syscall.SO_RCVBUF, 4*1024*1024); err != nil {
836
return nil, nil, err
837
}
838
server = os.NewFile(uintptr(serverFD), "server")
839
client = os.NewFile(uintptr(clientFD), "client")
840
runtime.SetFinalizer(server, func(*os.File) {
841
logrus.Debugf("Server network file GC'ed")
842
})
843
runtime.SetFinalizer(client, func(*os.File) {
844
logrus.Debugf("Client network file GC'ed")
845
})
846
vmNetworkFiles = append(vmNetworkFiles, server, client)
847
return server, client, nil
848
}
849
850