Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/driver/vz/vz_driver_darwin.go
2639 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
"embed"
11
"errors"
12
"fmt"
13
"net"
14
"path/filepath"
15
"runtime"
16
"time"
17
18
"github.com/Code-Hex/vz/v3"
19
"github.com/coreos/go-semver/semver"
20
"github.com/lima-vm/go-qcow2reader/image"
21
"github.com/lima-vm/go-qcow2reader/image/asif"
22
"github.com/lima-vm/go-qcow2reader/image/raw"
23
"github.com/sirupsen/logrus"
24
25
"github.com/lima-vm/lima/v2/pkg/driver"
26
"github.com/lima-vm/lima/v2/pkg/driverutil"
27
"github.com/lima-vm/lima/v2/pkg/hostagent/events"
28
"github.com/lima-vm/lima/v2/pkg/limatype"
29
"github.com/lima-vm/lima/v2/pkg/limayaml"
30
"github.com/lima-vm/lima/v2/pkg/osutil"
31
"github.com/lima-vm/lima/v2/pkg/ptr"
32
"github.com/lima-vm/lima/v2/pkg/reflectutil"
33
)
34
35
var knownYamlProperties = []string{
36
"AdditionalDisks",
37
"Arch",
38
"Audio",
39
"CACertificates",
40
"Containerd",
41
"CopyToHost",
42
"CPUs",
43
"CPUType",
44
"Disk",
45
"DNS",
46
"Env",
47
"Firmware",
48
"GuestInstallPrefix",
49
"HostResolver",
50
"Images",
51
"Memory",
52
"Message",
53
"MinimumLimaVersion",
54
"Mounts",
55
"MountType",
56
"MountTypesUnsupported",
57
"MountInotify",
58
"NestedVirtualization",
59
"Networks",
60
"OS",
61
"Param",
62
"Plain",
63
"PortForwards",
64
"Probes",
65
"PropagateProxyEnv",
66
"Provision",
67
"Rosetta",
68
"SSH",
69
"TimeZone",
70
"UpgradePackages",
71
"User",
72
"Video",
73
"VMType",
74
"VMOpts",
75
}
76
77
const Enabled = true
78
79
type LimaVzDriver struct {
80
Instance *limatype.Instance
81
82
SSHLocalPort int
83
vSockPort int
84
virtioPort string
85
rosettaEnabled bool
86
rosettaBinFmt bool
87
diskImageFormat image.Type
88
89
machine *virtualMachineWrapper
90
waitSSHLocalPortAccessible <-chan any
91
92
onVsockEvent func(*events.VsockEvent)
93
}
94
95
var (
96
_ driver.Driver = (*LimaVzDriver)(nil)
97
_ driver.VsockEventEmitter = (*LimaVzDriver)(nil)
98
)
99
100
// SetVsockEventCallback implements driver.VsockEventEmitter.
101
func (l *LimaVzDriver) SetVsockEventCallback(callback func(*events.VsockEvent)) {
102
l.onVsockEvent = callback
103
}
104
105
func New() *LimaVzDriver {
106
return &LimaVzDriver{
107
vSockPort: 2222,
108
virtioPort: "",
109
}
110
}
111
112
func (l *LimaVzDriver) Configure(inst *limatype.Instance) *driver.ConfiguredDriver {
113
l.Instance = inst
114
l.SSHLocalPort = inst.SSHLocalPort
115
116
if l.Instance.Config.MountType != nil {
117
mountTypesUnsupported := make(map[string]struct{})
118
for _, f := range l.Instance.Config.MountTypesUnsupported {
119
mountTypesUnsupported[f] = struct{}{}
120
}
121
122
if _, ok := mountTypesUnsupported[*l.Instance.Config.MountType]; ok {
123
// We cannot return an error here, but Validate() will return it.
124
logrus.Warnf("Unsupported mount type: %q", *l.Instance.Config.MountType)
125
}
126
}
127
128
var vzOpts limatype.VZOpts
129
if l.Instance.Config.VMOpts[limatype.VZ] != nil {
130
if err := limayaml.Convert(l.Instance.Config.VMOpts[limatype.VZ], &vzOpts, "vmOpts.vz"); err != nil {
131
logrus.WithError(err).Warnf("Couldn't convert %q", l.Instance.Config.VMOpts[limatype.VZ])
132
}
133
}
134
135
if runtime.GOOS == "darwin" && limayaml.IsNativeArch(limatype.AARCH64) {
136
if vzOpts.Rosetta.Enabled != nil {
137
l.rosettaEnabled = *vzOpts.Rosetta.Enabled
138
}
139
}
140
if vzOpts.Rosetta.BinFmt != nil {
141
l.rosettaBinFmt = *vzOpts.Rosetta.BinFmt
142
}
143
if vzOpts.DiskImageFormat != nil {
144
l.diskImageFormat = *vzOpts.DiskImageFormat
145
} else {
146
l.diskImageFormat = raw.Type
147
}
148
149
return &driver.ConfiguredDriver{
150
Driver: l,
151
}
152
}
153
154
func (l *LimaVzDriver) FillConfig(ctx context.Context, cfg *limatype.LimaYAML, _ string) error {
155
if cfg.VMType == nil {
156
cfg.VMType = ptr.Of(limatype.VZ)
157
}
158
159
if cfg.MountType == nil {
160
cfg.MountType = ptr.Of(limatype.VIRTIOFS)
161
}
162
163
if cfg.SSH.OverVsock == nil {
164
cfg.SSH.OverVsock = ptr.Of(true)
165
}
166
167
var vzOpts limatype.VZOpts
168
if err := limayaml.Convert(cfg.VMOpts[limatype.VZ], &vzOpts, "vmOpts.vz"); err != nil {
169
logrus.WithError(err).Warnf("Couldn't convert %q", cfg.VMOpts[limatype.VZ])
170
}
171
172
//nolint:staticcheck // Migration of top-level Rosetta if specified
173
if (vzOpts.Rosetta.Enabled == nil && vzOpts.Rosetta.BinFmt == nil) && (!isEmpty(cfg.Rosetta)) {
174
logrus.Debug("Migrating top-level Rosetta configuration to vmOpts.vz.rosetta")
175
vzOpts.Rosetta = cfg.Rosetta
176
}
177
//nolint:staticcheck // Warning about both top-level and vmOpts.vz.Rosetta being set
178
if (vzOpts.Rosetta.Enabled != nil && vzOpts.Rosetta.BinFmt != nil) && (!isEmpty(cfg.Rosetta)) {
179
logrus.Warn("Both top-level 'rosetta' and 'vmOpts.vz.rosetta' are configured. Using vmOpts.vz.rosetta. Top-level 'rosetta' is deprecated.")
180
}
181
182
if vzOpts.Rosetta.Enabled == nil {
183
vzOpts.Rosetta.Enabled = ptr.Of(false)
184
}
185
if vzOpts.Rosetta.BinFmt == nil {
186
vzOpts.Rosetta.BinFmt = ptr.Of(false)
187
}
188
if vzOpts.DiskImageFormat == nil {
189
vzOpts.DiskImageFormat = ptr.Of(raw.Type)
190
}
191
192
var opts any
193
if err := limayaml.Convert(vzOpts, &opts, ""); err != nil {
194
logrus.WithError(err).Warnf("Couldn't convert %+v", vzOpts)
195
}
196
if cfg.VMOpts == nil {
197
cfg.VMOpts = limatype.VMOpts{}
198
}
199
cfg.VMOpts[limatype.VZ] = opts
200
201
return validateConfig(ctx, cfg)
202
}
203
204
func isEmpty(r limatype.Rosetta) bool {
205
return r.Enabled == nil && r.BinFmt == nil
206
}
207
208
//go:embed boot/*.sh
209
var bootFS embed.FS
210
211
func (l *LimaVzDriver) BootScripts() (map[string][]byte, error) {
212
scripts := make(map[string][]byte)
213
214
entries, err := bootFS.ReadDir("boot")
215
if err == nil {
216
for _, entry := range entries {
217
if entry.IsDir() {
218
continue
219
}
220
221
content, err := bootFS.ReadFile("boot/" + entry.Name())
222
if err != nil {
223
return nil, err
224
}
225
226
scripts[entry.Name()] = content
227
}
228
}
229
230
return scripts, nil
231
}
232
233
func (l *LimaVzDriver) Validate(ctx context.Context) error {
234
return validateConfig(ctx, l.Instance.Config)
235
}
236
237
func validateConfig(_ context.Context, cfg *limatype.LimaYAML) error {
238
if cfg == nil {
239
return errors.New("configuration is nil")
240
}
241
macOSProductVersion, err := osutil.ProductVersion()
242
if err != nil {
243
return err
244
}
245
if macOSProductVersion.LessThan(*semver.New("13.0.0")) {
246
return errors.New("VZ driver requires macOS 13 or higher to run")
247
}
248
if runtime.GOARCH == "amd64" && macOSProductVersion.LessThan(*semver.New("15.5.0")) {
249
logrus.Warnf("vmType %s: On Intel Mac, macOS 15.5 or later is required to run Linux 6.12 or later. "+
250
"Update macOS, or change vmType to \"qemu\" if the VM does not start up. (https://github.com/lima-vm/lima/issues/3334)",
251
*cfg.VMType)
252
}
253
if cfg.MountType != nil && *cfg.MountType == limatype.NINEP {
254
return fmt.Errorf("field `mountType` must be %q or %q for VZ driver , got %q", limatype.REVSSHFS, limatype.VIRTIOFS, *cfg.MountType)
255
}
256
if *cfg.Firmware.LegacyBIOS {
257
logrus.Warnf("vmType %s: ignoring `firmware.legacyBIOS`", *cfg.VMType)
258
}
259
for _, f := range cfg.Firmware.Images {
260
switch f.VMType {
261
case "", limatype.VZ:
262
if f.Arch == *cfg.Arch {
263
return errors.New("`firmware.images` configuration is not supported for VZ driver")
264
}
265
}
266
}
267
if unknown := reflectutil.UnknownNonEmptyFields(cfg, knownYamlProperties...); cfg.VMType != nil && len(unknown) > 0 {
268
logrus.Warnf("vmType %s: ignoring %+v", *cfg.VMType, unknown)
269
}
270
271
if !limayaml.IsNativeArch(*cfg.Arch) {
272
return fmt.Errorf("unsupported arch: %q", *cfg.Arch)
273
}
274
275
for i, image := range cfg.Images {
276
if unknown := reflectutil.UnknownNonEmptyFields(image, "File", "Kernel", "Initrd"); len(unknown) > 0 {
277
logrus.Warnf("vmType %s: ignoring images[%d]: %+v", *cfg.VMType, i, unknown)
278
}
279
}
280
281
for i, mount := range cfg.Mounts {
282
if unknown := reflectutil.UnknownNonEmptyFields(mount, "Location",
283
"MountPoint",
284
"Writable",
285
"SSHFS",
286
"NineP",
287
); len(unknown) > 0 {
288
logrus.Warnf("vmType %s: ignoring mounts[%d]: %+v", *cfg.VMType, i, unknown)
289
}
290
}
291
292
for i, nw := range cfg.Networks {
293
if unknown := reflectutil.UnknownNonEmptyFields(nw, "VZNAT",
294
"Lima",
295
"Socket",
296
"MACAddress",
297
"Metric",
298
"Interface",
299
); len(unknown) > 0 {
300
logrus.Warnf("vmType %s: ignoring networks[%d]: %+v", *cfg.VMType, i, unknown)
301
}
302
}
303
304
switch audioDevice := *cfg.Audio.Device; audioDevice {
305
case "":
306
case "vz", "default", "none":
307
default:
308
logrus.Warnf("field `audio.device` must be \"vz\", \"default\", or \"none\" for VZ driver, got %q", audioDevice)
309
}
310
311
switch videoDisplay := *cfg.Video.Display; videoDisplay {
312
case "vz", "default", "none":
313
default:
314
logrus.Warnf("field `video.display` must be \"vz\", \"default\", or \"none\" for VZ driver , got %q", videoDisplay)
315
}
316
var vzOpts limatype.VZOpts
317
if err := limayaml.Convert(cfg.VMOpts[limatype.VZ], &vzOpts, "vmOpts.vz"); err != nil {
318
logrus.WithError(err).Warnf("Couldn't convert %q", cfg.VMOpts[limatype.VZ])
319
}
320
switch *vzOpts.DiskImageFormat {
321
case raw.Type:
322
case asif.Type:
323
if macOSProductVersion.LessThan(*semver.New("26.0.0")) {
324
return fmt.Errorf("vmOpts.vz.diskImageFormat=%q requires macOS 26 or higher to run, got %q", asif.Type, macOSProductVersion)
325
}
326
default:
327
return fmt.Errorf("field `vmOpts.vz.diskImageFormat` must be %q or %q, got %q", raw.Type, asif.Type, *vzOpts.DiskImageFormat)
328
}
329
return nil
330
}
331
332
func (l *LimaVzDriver) Create(_ context.Context) error {
333
_, err := getMachineIdentifier(l.Instance)
334
return err
335
}
336
337
func (l *LimaVzDriver) CreateDisk(ctx context.Context) error {
338
return driverutil.EnsureDisk(ctx, l.Instance.Dir, *l.Instance.Config.Disk, l.diskImageFormat)
339
}
340
341
func (l *LimaVzDriver) Start(ctx context.Context) (chan error, error) {
342
logrus.Infof("Starting VZ (hint: to watch the boot progress, see %q)", filepath.Join(l.Instance.Dir, "serial*.log"))
343
vm, waitSSHLocalPortAccessible, errCh, err := startVM(ctx, l.Instance, l.SSHLocalPort, l.onVsockEvent)
344
if err != nil {
345
if errors.Is(err, vz.ErrUnsupportedOSVersion) {
346
return nil, fmt.Errorf("vz driver requires macOS 13 or higher to run: %w", err)
347
}
348
return nil, err
349
}
350
l.machine = vm
351
l.waitSSHLocalPortAccessible = waitSSHLocalPortAccessible
352
353
return errCh, nil
354
}
355
356
func (l *LimaVzDriver) canRunGUI() bool {
357
switch *l.Instance.Config.Video.Display {
358
case "vz", "default":
359
return true
360
default:
361
return false
362
}
363
}
364
365
func (l *LimaVzDriver) RunGUI() error {
366
if l.canRunGUI() {
367
return l.machine.StartGraphicApplication(1920, 1200)
368
}
369
return fmt.Errorf("RunGUI is not supported for the given driver '%s' and display '%s'", "vz", *l.Instance.Config.Video.Display)
370
}
371
372
func (l *LimaVzDriver) Stop(_ context.Context) error {
373
logrus.Info("Shutting down VZ")
374
canStop := l.machine.CanRequestStop()
375
376
if canStop {
377
_, err := l.machine.RequestStop()
378
if err != nil {
379
return err
380
}
381
382
timeout := time.After(5 * time.Second)
383
ticker := time.NewTicker(500 * time.Millisecond)
384
for {
385
select {
386
case <-timeout:
387
return errors.New("vz timeout while waiting for stop status")
388
case <-ticker.C:
389
l.machine.mu.Lock()
390
stopped := l.machine.stopped
391
l.machine.mu.Unlock()
392
if stopped {
393
return nil
394
}
395
}
396
}
397
}
398
399
return errors.New("vz: CanRequestStop is not supported")
400
}
401
402
func (l *LimaVzDriver) GuestAgentConn(_ context.Context) (net.Conn, string, error) {
403
for _, socket := range l.machine.SocketDevices() {
404
connect, err := socket.Connect(uint32(l.vSockPort))
405
return connect, "vsock", err
406
}
407
408
return nil, "", errors.New("unable to connect to guest agent via vsock port 2222")
409
}
410
411
func (l *LimaVzDriver) Info() driver.Info {
412
var info driver.Info
413
414
info.Name = "vz"
415
info.VsockPort = l.vSockPort
416
info.VirtioPort = l.virtioPort
417
if l.Instance != nil {
418
info.InstanceDir = l.Instance.Dir
419
}
420
421
var guiFlag bool
422
if l.Instance != nil {
423
guiFlag = l.canRunGUI()
424
}
425
info.Features = driver.DriverFeatures{
426
DynamicSSHAddress: false,
427
SkipSocketForwarding: false,
428
CanRunGUI: guiFlag,
429
RosettaEnabled: l.rosettaEnabled,
430
RosettaBinFmt: l.rosettaBinFmt,
431
}
432
return info
433
}
434
435
func (l *LimaVzDriver) SSHAddress(_ context.Context) (string, error) {
436
return "127.0.0.1", nil
437
}
438
439
func (l *LimaVzDriver) InspectStatus(_ context.Context, _ *limatype.Instance) string {
440
return ""
441
}
442
443
func (l *LimaVzDriver) Delete(_ context.Context) error {
444
return nil
445
}
446
447
func (l *LimaVzDriver) Register(_ context.Context) error {
448
return nil
449
}
450
451
func (l *LimaVzDriver) Unregister(_ context.Context) error {
452
return nil
453
}
454
455
func (l *LimaVzDriver) ChangeDisplayPassword(_ context.Context, _ string) error {
456
return nil
457
}
458
459
func (l *LimaVzDriver) DisplayConnection(_ context.Context) (string, error) {
460
return "", nil
461
}
462
463
func (l *LimaVzDriver) CreateSnapshot(_ context.Context, _ string) error {
464
return errUnimplemented
465
}
466
467
func (l *LimaVzDriver) ApplySnapshot(_ context.Context, _ string) error {
468
return errUnimplemented
469
}
470
471
func (l *LimaVzDriver) DeleteSnapshot(_ context.Context, _ string) error {
472
return errUnimplemented
473
}
474
475
func (l *LimaVzDriver) ListSnapshots(_ context.Context) (string, error) {
476
return "", errUnimplemented
477
}
478
479
func (l *LimaVzDriver) ForwardGuestAgent() bool {
480
// If driver is not providing, use host agent
481
return l.vSockPort == 0 && l.virtioPort == ""
482
}
483
484
func (l *LimaVzDriver) AdditionalSetupForSSH(_ context.Context) error {
485
<-l.waitSSHLocalPortAccessible
486
return nil
487
}
488
489