Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/driver/qemu/qemu_driver.go
2644 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package qemu
5
6
import (
7
"bufio"
8
"bytes"
9
"context"
10
"errors"
11
"fmt"
12
"io"
13
"io/fs"
14
"net"
15
"os"
16
"os/exec"
17
"path/filepath"
18
"runtime"
19
"slices"
20
"strconv"
21
"strings"
22
"text/template"
23
"time"
24
25
"github.com/coreos/go-semver/semver"
26
"github.com/digitalocean/go-qemu/qmp"
27
"github.com/digitalocean/go-qemu/qmp/raw"
28
"github.com/sirupsen/logrus"
29
30
"github.com/lima-vm/lima/v2/pkg/driver"
31
"github.com/lima-vm/lima/v2/pkg/driver/qemu/entitlementutil"
32
"github.com/lima-vm/lima/v2/pkg/executil"
33
"github.com/lima-vm/lima/v2/pkg/limatype"
34
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
35
"github.com/lima-vm/lima/v2/pkg/limayaml"
36
"github.com/lima-vm/lima/v2/pkg/networks/usernet"
37
"github.com/lima-vm/lima/v2/pkg/osutil"
38
"github.com/lima-vm/lima/v2/pkg/ptr"
39
"github.com/lima-vm/lima/v2/pkg/reflectutil"
40
"github.com/lima-vm/lima/v2/pkg/version/versionutil"
41
)
42
43
type LimaQemuDriver struct {
44
Instance *limatype.Instance
45
SSHLocalPort int
46
vSockPort int
47
virtioPort string
48
49
qCmd *exec.Cmd
50
qWaitCh chan error
51
52
vhostCmds []*exec.Cmd
53
}
54
55
var _ driver.Driver = (*LimaQemuDriver)(nil)
56
57
func New() *LimaQemuDriver {
58
// virtserialport doesn't seem to work reliably: https://github.com/lima-vm/lima/issues/2064
59
// but on Windows default Unix socket forwarding is not available
60
var virtioPort string
61
virtioPort = filenames.VirtioPort
62
if runtime.GOOS != "windows" {
63
virtioPort = ""
64
}
65
return &LimaQemuDriver{
66
vSockPort: 0,
67
virtioPort: virtioPort,
68
}
69
}
70
71
func (l *LimaQemuDriver) Configure(inst *limatype.Instance) *driver.ConfiguredDriver {
72
l.Instance = inst
73
l.SSHLocalPort = inst.SSHLocalPort
74
75
return &driver.ConfiguredDriver{
76
Driver: l,
77
}
78
}
79
80
func (l *LimaQemuDriver) Validate(ctx context.Context) error {
81
if err := validateArch(ctx, l.Instance.Config); err != nil {
82
return err
83
}
84
return validateConfig(l.Instance.Config)
85
}
86
87
func validateConfig(cfg *limatype.LimaYAML) error {
88
if cfg == nil {
89
return errors.New("configuration is nil")
90
}
91
if err := validateMountType(cfg); err != nil {
92
return err
93
}
94
95
for i, nw := range cfg.Networks {
96
if unknown := reflectutil.UnknownNonEmptyFields(nw,
97
"Lima",
98
"Socket",
99
"MACAddress",
100
"Metric",
101
"Interface",
102
); len(unknown) > 0 {
103
logrus.Warnf("vmType %s: ignoring networks[%d]: %+v", *cfg.VMType, i, unknown)
104
}
105
}
106
107
var qemuOpts limatype.QEMUOpts
108
if err := limayaml.Convert(cfg.VMOpts[limatype.QEMU], &qemuOpts, "vmOpts.qemu"); err != nil {
109
return err
110
}
111
if qemuOpts.MinimumVersion != nil {
112
if _, err := semver.NewVersion(*qemuOpts.MinimumVersion); err != nil {
113
return fmt.Errorf("field `vmOpts.qemu.minimumVersion` must be a semvar value, got %q: %w", *qemuOpts.MinimumVersion, err)
114
}
115
}
116
117
return nil
118
}
119
120
// Helper method for checking the binary signature on macOS.
121
func validateArch(ctx context.Context, cfg *limatype.LimaYAML) error {
122
if runtime.GOOS == "darwin" {
123
var vmArch string
124
if cfg.Arch != nil {
125
vmArch = *cfg.Arch
126
}
127
if err := checkBinarySignature(ctx, vmArch); err != nil {
128
return err
129
}
130
}
131
return nil
132
}
133
134
// Helper method for mount type validation.
135
func validateMountType(cfg *limatype.LimaYAML) error {
136
if cfg.MountType != nil && *cfg.MountType == limatype.VIRTIOFS && runtime.GOOS != "linux" {
137
return fmt.Errorf("field `mountType` must be %q or %q for QEMU driver on non-Linux, got %q",
138
limatype.REVSSHFS, limatype.NINEP, *cfg.MountType)
139
}
140
if cfg.MountTypesUnsupported != nil && cfg.MountType != nil && slices.Contains(cfg.MountTypesUnsupported, *cfg.MountType) {
141
return fmt.Errorf("mount type %q is explicitly unsupported", *cfg.MountType)
142
}
143
if runtime.GOOS == "windows" && cfg.MountType != nil && *cfg.MountType == limatype.NINEP {
144
return fmt.Errorf("mount type %q is not supported on Windows", limatype.NINEP)
145
}
146
147
return nil
148
}
149
150
func (l *LimaQemuDriver) FillConfig(_ context.Context, cfg *limatype.LimaYAML, filePath string) error {
151
if cfg.VMType == nil {
152
cfg.VMType = ptr.Of(limatype.QEMU)
153
}
154
155
instDir := filepath.Dir(filePath)
156
157
if cfg.Video.VNC.Display == nil || *cfg.Video.VNC.Display == "" {
158
cfg.Video.VNC.Display = ptr.Of("127.0.0.1:0,to=9")
159
}
160
161
var qemuOpts limatype.QEMUOpts
162
if err := limayaml.Convert(cfg.VMOpts[limatype.QEMU], &qemuOpts, "vmOpts.qemu"); err != nil {
163
logrus.WithError(err).Warnf("Couldn't convert %q", cfg.VMOpts[limatype.QEMU])
164
}
165
if qemuOpts.CPUType == nil {
166
qemuOpts.CPUType = limatype.CPUType{}
167
}
168
169
//nolint:staticcheck // Migration of top-level CPUTYPE if specified
170
if len(cfg.CPUType) > 0 {
171
logrus.Warn("The top-level `cpuType` field is deprecated and will be removed in a future release. Please migrate to `vmOpts.qemu.cpuType`.")
172
for arch, v := range cfg.CPUType {
173
if v == "" {
174
continue
175
}
176
if existing, ok := qemuOpts.CPUType[arch]; ok && existing != "" && existing != v {
177
logrus.Warnf("Conflicting cpuType for arch %q: top-level=%q, vmOpts.qemu=%q; using vmOpts.qemu value", arch, v, existing)
178
continue
179
}
180
qemuOpts.CPUType[arch] = v
181
}
182
cfg.CPUType = nil
183
184
var opts any
185
if err := limayaml.Convert(qemuOpts, &opts, ""); err != nil {
186
logrus.WithError(err).Warnf("Couldn't convert %+v", qemuOpts)
187
}
188
if cfg.VMOpts == nil {
189
cfg.VMOpts = limatype.VMOpts{}
190
}
191
cfg.VMOpts[limatype.QEMU] = opts
192
}
193
194
mountTypesUnsupported := make(map[string]struct{})
195
for _, f := range cfg.MountTypesUnsupported {
196
mountTypesUnsupported[f] = struct{}{}
197
}
198
199
if runtime.GOOS == "windows" {
200
// QEMU for Windows does not support 9p
201
mountTypesUnsupported[limatype.NINEP] = struct{}{}
202
}
203
204
if cfg.MountType == nil || *cfg.MountType == "" || *cfg.MountType == "default" {
205
cfg.MountType = ptr.Of(limatype.NINEP)
206
if _, ok := mountTypesUnsupported[limatype.NINEP]; ok {
207
// Use REVSSHFS if the instance does not support 9p
208
cfg.MountType = ptr.Of(limatype.REVSSHFS)
209
} else if limayaml.IsExistingInstanceDir(instDir) && !versionutil.GreaterEqual(limayaml.ExistingLimaVersion(instDir), "1.0.0") {
210
// Use REVSSHFS if the instance was created with Lima prior to v1.0
211
cfg.MountType = ptr.Of(limatype.REVSSHFS)
212
}
213
}
214
215
for i := range cfg.Mounts {
216
mount := &cfg.Mounts[i]
217
if mount.Virtiofs.QueueSize == nil && *cfg.MountType == limatype.VIRTIOFS {
218
cfg.Mounts[i].Virtiofs.QueueSize = ptr.Of(limayaml.DefaultVirtiofsQueueSize)
219
}
220
}
221
222
if _, ok := mountTypesUnsupported[*cfg.MountType]; ok {
223
return fmt.Errorf("mount type %q is explicitly unsupported", *cfg.MountType)
224
}
225
226
return validateConfig(cfg)
227
}
228
229
func (l *LimaQemuDriver) BootScripts() (map[string][]byte, error) {
230
return nil, nil
231
}
232
233
func (l *LimaQemuDriver) CreateDisk(ctx context.Context) error {
234
qCfg := Config{
235
Name: l.Instance.Name,
236
InstanceDir: l.Instance.Dir,
237
LimaYAML: l.Instance.Config,
238
}
239
return EnsureDisk(ctx, qCfg)
240
}
241
242
func (l *LimaQemuDriver) Start(_ context.Context) (chan error, error) {
243
ctx, cancel := context.WithCancel(context.Background())
244
defer func() {
245
if l.qCmd == nil {
246
cancel()
247
}
248
}()
249
250
if l.Instance.Config.SSH.OverVsock != nil && *l.Instance.Config.SSH.OverVsock {
251
logrus.Warn(".ssh.overVsock is not implemented yet for QEMU driver")
252
}
253
254
qCfg := Config{
255
Name: l.Instance.Name,
256
InstanceDir: l.Instance.Dir,
257
LimaYAML: l.Instance.Config,
258
SSHLocalPort: l.SSHLocalPort,
259
SSHAddress: l.Instance.SSHAddress,
260
VirtioGA: l.virtioPort != "",
261
}
262
qExe, qArgs, err := Cmdline(ctx, qCfg)
263
if err != nil {
264
return nil, err
265
}
266
267
var vhostCmds []*exec.Cmd
268
if *l.Instance.Config.MountType == limatype.VIRTIOFS {
269
vhostExe, err := FindVirtiofsd(ctx, qExe)
270
if err != nil {
271
return nil, err
272
}
273
274
for i := range l.Instance.Config.Mounts {
275
args, err := VirtiofsdCmdline(qCfg, i)
276
if err != nil {
277
return nil, err
278
}
279
280
vhostCmds = append(vhostCmds, exec.CommandContext(ctx, vhostExe, args...))
281
}
282
}
283
284
var qArgsFinal []string
285
applier := &qArgTemplateApplier{}
286
for _, unapplied := range qArgs {
287
applied, err := applier.applyTemplate(unapplied)
288
if err != nil {
289
return nil, err
290
}
291
qArgsFinal = append(qArgsFinal, applied)
292
}
293
qCmd := exec.CommandContext(ctx, qExe, qArgsFinal...)
294
qCmd.ExtraFiles = append(qCmd.ExtraFiles, applier.files...)
295
qCmd.SysProcAttr = executil.BackgroundSysProcAttr
296
qStdout, err := qCmd.StdoutPipe()
297
if err != nil {
298
return nil, err
299
}
300
go logPipeRoutine(qStdout, "qemu[stdout]")
301
qStderr, err := qCmd.StderrPipe()
302
if err != nil {
303
return nil, err
304
}
305
go logPipeRoutine(qStderr, "qemu[stderr]")
306
307
for i, vhostCmd := range vhostCmds {
308
vhostStdout, err := vhostCmd.StdoutPipe()
309
if err != nil {
310
return nil, err
311
}
312
go logPipeRoutine(vhostStdout, fmt.Sprintf("virtiofsd-%d[stdout]", i))
313
vhostStderr, err := vhostCmd.StderrPipe()
314
if err != nil {
315
return nil, err
316
}
317
go logPipeRoutine(vhostStderr, fmt.Sprintf("virtiofsd-%d[stderr]", i))
318
}
319
320
for i, vhostCmd := range vhostCmds {
321
logrus.Debugf("vhostCmd[%d].Args: %v", i, vhostCmd.Args)
322
if err := vhostCmd.Start(); err != nil {
323
return nil, err
324
}
325
326
vhostWaitCh := make(chan error)
327
go func() {
328
vhostWaitCh <- vhostCmd.Wait()
329
}()
330
331
vhostSock := filepath.Join(l.Instance.Dir, fmt.Sprintf(filenames.VhostSock, i))
332
vhostSockExists := false
333
for attempt := range 5 {
334
logrus.Debugf("Try waiting for %s to appear (attempt %d)", vhostSock, attempt)
335
336
if _, err := os.Stat(vhostSock); err != nil {
337
if !errors.Is(err, fs.ErrNotExist) {
338
logrus.Warnf("Failed to check for vhost socket: %v", err)
339
}
340
} else {
341
vhostSockExists = true
342
break
343
}
344
345
retry := time.NewTimer(200 * time.Millisecond)
346
select {
347
case err = <-vhostWaitCh:
348
return nil, fmt.Errorf("virtiofsd never created vhost socket: %w", err)
349
case <-retry.C:
350
}
351
}
352
353
if !vhostSockExists {
354
return nil, fmt.Errorf("vhost socket %s never appeared", vhostSock)
355
}
356
357
go func() {
358
if err := <-vhostWaitCh; err != nil {
359
logrus.Errorf("Error from virtiofsd instance #%d: %v", i, err)
360
}
361
}()
362
}
363
364
logrus.Infof("Starting QEMU (hint: to watch the boot progress, see %q)", filepath.Join(qCfg.InstanceDir, "serial*.log"))
365
logrus.Debugf("qCmd.Args: %v", qCmd.Args)
366
if err := qCmd.Start(); err != nil {
367
return nil, err
368
}
369
l.qCmd = qCmd
370
l.qWaitCh = make(chan error, 1)
371
go func() {
372
defer close(l.qWaitCh)
373
defer cancel()
374
l.qWaitCh <- qCmd.Wait()
375
}()
376
l.vhostCmds = vhostCmds
377
go func() {
378
if usernetIndex := limayaml.FirstUsernetIndex(l.Instance.Config); usernetIndex != -1 {
379
client := usernet.NewClientByName(l.Instance.Config.Networks[usernetIndex].Lima)
380
err := client.ConfigureDriver(ctx, l.Instance, l.SSHLocalPort)
381
if err != nil {
382
l.qWaitCh <- err
383
}
384
}
385
}()
386
return l.qWaitCh, nil
387
}
388
389
func (l *LimaQemuDriver) Stop(ctx context.Context) error {
390
return l.shutdownQEMU(ctx, 3*time.Minute, l.qCmd, l.qWaitCh)
391
}
392
393
func (l *LimaQemuDriver) ChangeDisplayPassword(_ context.Context, password string) error {
394
return l.changeVNCPassword(password)
395
}
396
397
func (l *LimaQemuDriver) DisplayConnection(_ context.Context) (string, error) {
398
return l.getVNCDisplayPort()
399
}
400
401
func waitFileExists(path string, timeout time.Duration) error {
402
startWaiting := time.Now()
403
for {
404
_, err := os.Stat(path)
405
if err == nil {
406
break
407
}
408
if !errors.Is(err, os.ErrNotExist) {
409
return err
410
}
411
if time.Since(startWaiting) > timeout {
412
return fmt.Errorf("timeout waiting for %s", path)
413
}
414
time.Sleep(500 * time.Millisecond)
415
}
416
return nil
417
}
418
419
// Ask the user to sign the qemu binary with the "com.apple.security.hypervisor" if needed.
420
// Workaround for https://github.com/lima-vm/lima/issues/1742
421
func checkBinarySignature(ctx context.Context, vmArch string) error {
422
macOSProductVersion, err := osutil.ProductVersion()
423
if err != nil {
424
return err
425
}
426
// The codesign --xml option is only available on macOS Monterey and later
427
if !macOSProductVersion.LessThan(*semver.New("12.0.0")) && vmArch != "" {
428
qExe, _, err := Exe(vmArch)
429
if err != nil {
430
return fmt.Errorf("failed to find the QEMU binary for the architecture %q: %w", vmArch, err)
431
}
432
if accel := Accel(vmArch); accel == "hvf" {
433
entitlementutil.AskToSignIfNotSignedProperly(ctx, qExe)
434
}
435
}
436
437
return nil
438
}
439
440
func (l *LimaQemuDriver) changeVNCPassword(password string) error {
441
qmpSockPath := filepath.Join(l.Instance.Dir, filenames.QMPSock)
442
err := waitFileExists(qmpSockPath, 30*time.Second)
443
if err != nil {
444
return err
445
}
446
qmpClient, err := qmp.NewSocketMonitor("unix", qmpSockPath, 5*time.Second)
447
if err != nil {
448
return err
449
}
450
if err := qmpClient.Connect(); err != nil {
451
return err
452
}
453
defer func() { _ = qmpClient.Disconnect() }()
454
rawClient := raw.NewMonitor(qmpClient)
455
err = rawClient.ChangeVNCPassword(password)
456
if err != nil {
457
return err
458
}
459
return nil
460
}
461
462
func (l *LimaQemuDriver) getVNCDisplayPort() (string, error) {
463
qmpSockPath := filepath.Join(l.Instance.Dir, filenames.QMPSock)
464
qmpClient, err := qmp.NewSocketMonitor("unix", qmpSockPath, 5*time.Second)
465
if err != nil {
466
return "", err
467
}
468
if err := qmpClient.Connect(); err != nil {
469
return "", err
470
}
471
defer func() { _ = qmpClient.Disconnect() }()
472
rawClient := raw.NewMonitor(qmpClient)
473
info, err := rawClient.QueryVNC()
474
if err != nil {
475
return "", err
476
}
477
return *info.Service, nil
478
}
479
480
func (l *LimaQemuDriver) removeVNCFiles() error {
481
vncfile := filepath.Join(l.Instance.Dir, filenames.VNCDisplayFile)
482
err := os.RemoveAll(vncfile)
483
if err != nil {
484
return err
485
}
486
vncpwdfile := filepath.Join(l.Instance.Dir, filenames.VNCPasswordFile)
487
err = os.RemoveAll(vncpwdfile)
488
if err != nil {
489
return err
490
}
491
return nil
492
}
493
494
func (l *LimaQemuDriver) killVhosts() error {
495
var errs []error
496
for i, vhost := range l.vhostCmds {
497
if err := vhost.Process.Kill(); err != nil && !errors.Is(err, os.ErrProcessDone) {
498
errs = append(errs, fmt.Errorf("failed to kill virtiofsd instance #%d: %w", i, err))
499
}
500
}
501
502
return errors.Join(errs...)
503
}
504
505
func (l *LimaQemuDriver) shutdownQEMU(ctx context.Context, timeout time.Duration, qCmd *exec.Cmd, qWaitCh <-chan error) error {
506
// "power button" refers to ACPI on the most archs, except RISC-V
507
logrus.Info("Shutting down QEMU with the power button")
508
if usernetIndex := limayaml.FirstUsernetIndex(l.Instance.Config); usernetIndex != -1 {
509
client := usernet.NewClientByName(l.Instance.Config.Networks[usernetIndex].Lima)
510
err := client.UnExposeSSH(l.SSHLocalPort)
511
if err != nil {
512
logrus.Warnf("Failed to remove SSH binding for port %d", l.SSHLocalPort)
513
}
514
}
515
qmpSockPath := filepath.Join(l.Instance.Dir, filenames.QMPSock)
516
qmpClient, err := qmp.NewSocketMonitor("unix", qmpSockPath, 5*time.Second)
517
if err != nil {
518
logrus.WithError(err).Warnf("failed to open the QMP socket %q, forcibly killing QEMU", qmpSockPath)
519
return l.killQEMU(ctx, timeout, qCmd, qWaitCh)
520
}
521
if err := qmpClient.Connect(); err != nil {
522
logrus.WithError(err).Warnf("failed to connect to the QMP socket %q, forcibly killing QEMU", qmpSockPath)
523
return l.killQEMU(ctx, timeout, qCmd, qWaitCh)
524
}
525
defer func() { _ = qmpClient.Disconnect() }()
526
rawClient := raw.NewMonitor(qmpClient)
527
logrus.Info("Sending QMP system_powerdown command")
528
if err := rawClient.SystemPowerdown(); err != nil {
529
logrus.WithError(err).Warnf("failed to send system_powerdown command via the QMP socket %q, forcibly killing QEMU", qmpSockPath)
530
return l.killQEMU(ctx, timeout, qCmd, qWaitCh)
531
}
532
timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), timeout)
533
defer timeoutCancel()
534
535
select {
536
case qWaitErr, ok := <-qWaitCh:
537
if !ok {
538
logrus.Info("QEMU wait channel was closed")
539
_ = l.removeVNCFiles()
540
return l.killVhosts()
541
}
542
entry := logrus.NewEntry(logrus.StandardLogger())
543
if qWaitErr != nil {
544
entry = entry.WithError(qWaitErr)
545
}
546
entry.Info("QEMU has exited")
547
_ = l.removeVNCFiles()
548
return errors.Join(qWaitErr, l.killVhosts())
549
case <-timeoutCtx.Done():
550
if qCmd.ProcessState != nil {
551
logrus.Info("QEMU has already exited")
552
_ = l.removeVNCFiles()
553
return l.killVhosts()
554
}
555
logrus.Warnf("QEMU did not exit in %v, forcibly killing QEMU", timeout)
556
return l.killQEMU(ctx, timeout, qCmd, qWaitCh)
557
}
558
}
559
560
func (l *LimaQemuDriver) killQEMU(_ context.Context, _ time.Duration, qCmd *exec.Cmd, qWaitCh <-chan error) error {
561
var qWaitErr error
562
if qCmd.ProcessState == nil {
563
if killErr := qCmd.Process.Kill(); killErr != nil {
564
logrus.WithError(killErr).Warn("failed to kill QEMU")
565
}
566
qWaitErr = <-qWaitCh
567
logrus.WithError(qWaitErr).Info("QEMU has exited, after killing forcibly")
568
} else {
569
logrus.Info("QEMU has already exited")
570
}
571
qemuPIDPath := filepath.Join(l.Instance.Dir, filenames.PIDFile(*l.Instance.Config.VMType))
572
_ = os.RemoveAll(qemuPIDPath)
573
_ = l.removeVNCFiles()
574
return errors.Join(qWaitErr, l.killVhosts())
575
}
576
577
func logPipeRoutine(r io.Reader, header string) {
578
scanner := bufio.NewScanner(r)
579
for scanner.Scan() {
580
line := scanner.Text()
581
logrus.Debugf("%s: %s", header, line)
582
}
583
}
584
585
func (l *LimaQemuDriver) DeleteSnapshot(ctx context.Context, tag string) error {
586
qCfg := Config{
587
Name: l.Instance.Name,
588
InstanceDir: l.Instance.Dir,
589
LimaYAML: l.Instance.Config,
590
}
591
return Del(ctx, qCfg, l.Instance.Status == limatype.StatusRunning, tag)
592
}
593
594
func (l *LimaQemuDriver) CreateSnapshot(ctx context.Context, tag string) error {
595
qCfg := Config{
596
Name: l.Instance.Name,
597
InstanceDir: l.Instance.Dir,
598
LimaYAML: l.Instance.Config,
599
}
600
return Save(ctx, qCfg, l.Instance.Status == limatype.StatusRunning, tag)
601
}
602
603
func (l *LimaQemuDriver) ApplySnapshot(ctx context.Context, tag string) error {
604
qCfg := Config{
605
Name: l.Instance.Name,
606
InstanceDir: l.Instance.Dir,
607
LimaYAML: l.Instance.Config,
608
}
609
return Load(ctx, qCfg, l.Instance.Status == limatype.StatusRunning, tag)
610
}
611
612
func (l *LimaQemuDriver) ListSnapshots(ctx context.Context) (string, error) {
613
qCfg := Config{
614
Name: l.Instance.Name,
615
InstanceDir: l.Instance.Dir,
616
LimaYAML: l.Instance.Config,
617
}
618
return List(ctx, qCfg, l.Instance.Status == limatype.StatusRunning)
619
}
620
621
func (l *LimaQemuDriver) GuestAgentConn(ctx context.Context) (net.Conn, string, error) {
622
var d net.Dialer
623
dialContext, err := d.DialContext(ctx, "unix", filepath.Join(l.Instance.Dir, filenames.GuestAgentSock))
624
return dialContext, "unix", err
625
}
626
627
type qArgTemplateApplier struct {
628
files []*os.File
629
}
630
631
func (a *qArgTemplateApplier) applyTemplate(qArg string) (string, error) {
632
if !strings.Contains(qArg, "{{") {
633
return qArg, nil
634
}
635
funcMap := template.FuncMap{
636
"fd_connect": func(v any) string {
637
fn := func(v any) (string, error) {
638
s, ok := v.(string)
639
if !ok {
640
return "", fmt.Errorf("non-string argument %+v", v)
641
}
642
addr, err := net.ResolveUnixAddr("unix", s)
643
if err != nil {
644
return "", err
645
}
646
conn, err := net.DialUnix("unix", nil, addr)
647
if err != nil {
648
return "", err
649
}
650
f, err := conn.File()
651
if err != nil {
652
return "", err
653
}
654
if err := conn.Close(); err != nil {
655
return "", err
656
}
657
a.files = append(a.files, f)
658
fd := len(a.files) + 2 // the first FD is 3
659
return strconv.Itoa(fd), nil
660
}
661
res, err := fn(v)
662
if err != nil {
663
panic(fmt.Errorf("fd_connect: %w", err))
664
}
665
return res
666
},
667
}
668
tmpl, err := template.New("").Funcs(funcMap).Parse(qArg)
669
if err != nil {
670
return "", err
671
}
672
var b bytes.Buffer
673
if err := tmpl.Execute(&b, nil); err != nil {
674
return "", err
675
}
676
return b.String(), nil
677
}
678
679
func (l *LimaQemuDriver) Info() driver.Info {
680
var info driver.Info
681
info.Name = "qemu"
682
if l.Instance != nil && l.Instance.Dir != "" {
683
info.InstanceDir = l.Instance.Dir
684
}
685
info.VirtioPort = l.virtioPort
686
info.VsockPort = l.vSockPort
687
688
info.Features = driver.DriverFeatures{
689
DynamicSSHAddress: false,
690
SkipSocketForwarding: false,
691
CanRunGUI: false,
692
}
693
return info
694
}
695
696
func (l *LimaQemuDriver) SSHAddress(_ context.Context) (string, error) {
697
return "127.0.0.1", nil
698
}
699
700
func (l *LimaQemuDriver) InspectStatus(_ context.Context, _ *limatype.Instance) string {
701
return ""
702
}
703
704
func (l *LimaQemuDriver) Create(_ context.Context) error {
705
return nil
706
}
707
708
func (l *LimaQemuDriver) Delete(_ context.Context) error {
709
return nil
710
}
711
712
func (l *LimaQemuDriver) RunGUI() error {
713
return nil
714
}
715
716
func (l *LimaQemuDriver) Register(_ context.Context) error {
717
return nil
718
}
719
720
func (l *LimaQemuDriver) Unregister(_ context.Context) error {
721
return nil
722
}
723
724
func (l *LimaQemuDriver) ForwardGuestAgent() bool {
725
// if driver is not providing, use host agent
726
return l.vSockPort == 0 && l.virtioPort == ""
727
}
728
729
func (l *LimaQemuDriver) AdditionalSetupForSSH(_ context.Context) error {
730
return nil
731
}
732
733