Skip to content

Commit 98b4452

Browse files
authored
Merge pull request #148 from AkihiroSuda/fix-146
`vde: defer vnl validation until starting qemu` + `pkg/start: exit when hostagent process exited`
2 parents 30b47c3 + 1349aff commit 98b4452

File tree

3 files changed

+113
-26
lines changed

3 files changed

+113
-26
lines changed

pkg/limayaml/validate.go

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -179,31 +179,31 @@ func validateNetwork(yNetwork Network) error {
179179
// optionally with vde:// prefix.
180180
if !strings.Contains(vde.VNL, "://") || strings.HasPrefix(vde.VNL, "vde://") {
181181
vdeSwitch := strings.TrimPrefix(vde.VNL, "vde://")
182-
fi, err := os.Stat(vdeSwitch)
183-
if err != nil {
184-
return fmt.Errorf("field `%s.vnl` %q failed stat: %w", field, vdeSwitch, err)
185-
}
186-
if fi.IsDir() {
187-
/* Switch mode (vdeSwitch is dir, port != 65535) */
188-
ctlSocket := filepath.Join(vdeSwitch, "ctl")
189-
fi, err = os.Stat(ctlSocket)
190-
if err != nil {
191-
return fmt.Errorf("field `%s.vnl` control socket %q failed stat: %w", field, ctlSocket, err)
192-
}
193-
if fi.Mode()&os.ModeSocket == 0 {
194-
return fmt.Errorf("field `%s.vnl` file %q is not a UNIX socket", field, ctlSocket)
195-
}
196-
if vde.SwitchPort == 65535 {
197-
return fmt.Errorf("field `%s.vnl` points to a non-PTP switch, so the port number must not be 65535", field)
198-
}
182+
if fi, err := os.Stat(vdeSwitch); err != nil {
183+
// negligible when the instance is stopped
184+
logrus.WithError(err).Debugf("field `%s.vnl` %q failed stat", field, vdeSwitch)
199185
} else {
200-
/* PTP mode (vdeSwitch is socket, port == 65535) */
201-
if fi.Mode()&os.ModeSocket == 0 {
202-
return fmt.Errorf("field `%s.vnl` %q is not a directory nor a UNIX socket", field, vdeSwitch)
203-
}
204-
if vde.SwitchPort != 65535 {
205-
return fmt.Errorf("field `%s.vnl` points to a PTP (switchless) socket %q, so the port number has to be 65535 (got %d)",
206-
field, vdeSwitch, vde.SwitchPort)
186+
if fi.IsDir() {
187+
/* Switch mode (vdeSwitch is dir, port != 65535) */
188+
ctlSocket := filepath.Join(vdeSwitch, "ctl")
189+
// ErrNotExist during os.Stat(ctlSocket) can be ignored. ctlSocket does not need to exist until actually starting the VM
190+
if fi, err = os.Stat(ctlSocket); err == nil {
191+
if fi.Mode()&os.ModeSocket == 0 {
192+
return fmt.Errorf("field `%s.vnl` file %q is not a UNIX socket", field, ctlSocket)
193+
}
194+
}
195+
if vde.SwitchPort == 65535 {
196+
return fmt.Errorf("field `%s.vnl` points to a non-PTP switch, so the port number must not be 65535", field)
197+
}
198+
} else {
199+
/* PTP mode (vdeSwitch is socket, port == 65535) */
200+
if fi.Mode()&os.ModeSocket == 0 {
201+
return fmt.Errorf("field `%s.vnl` %q is not a directory nor a UNIX socket", field, vdeSwitch)
202+
}
203+
if vde.SwitchPort != 65535 {
204+
return fmt.Errorf("field `%s.vnl` points to a PTP (switchless) socket %q, so the port number has to be 65535 (got %d)",
205+
field, vdeSwitch, vde.SwitchPort)
206+
}
207207
}
208208
}
209209
} else if runtime.GOOS != "linux" {

pkg/qemu/qemu.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package qemu
22

33
import (
4+
"bytes"
45
"errors"
56
"fmt"
7+
"io/fs"
68
"os"
79
"os/exec"
810
"path/filepath"
@@ -136,15 +138,64 @@ func appendArgsIfNoConflict(args []string, k, v string) []string {
136138
}
137139
return append(args, k, v)
138140
}
141+
142+
type features struct {
143+
// NetdevHelp is the output of `qemu-system-x86_64 -accel help`
144+
// e.g. "Accelerators supported in QEMU binary:\ntcg\nhax\nhvf\n"
145+
// Not machine-readable, but checking strings.Contains() should be fine.
146+
AccelHelp []byte
147+
// NetdevHelp is the output of `qemu-system-x86_64 -netdev help`
148+
// e.g. "Available netdev backend types:\nsocket\nhubport\ntap\nuser\nvde\nbridge\vhost-user\n"
149+
// Not machine-readable, but checking strings.Contains() should be fine.
150+
NetdevHelp []byte
151+
}
152+
153+
func inspectFeatures(exe string) (*features, error) {
154+
var (
155+
f features
156+
stdout bytes.Buffer
157+
stderr bytes.Buffer
158+
)
159+
cmd := exec.Command(exe, "-M", "none", "-accel", "help")
160+
cmd.Stdout = &stdout
161+
cmd.Stderr = &stderr
162+
if err := cmd.Run(); err != nil {
163+
return nil, fmt.Errorf("failed to run %v: stdout=%q, stderr=%q", cmd.Args, stdout.String(), stderr.String())
164+
}
165+
f.AccelHelp = stdout.Bytes()
166+
167+
cmd = exec.Command(exe, "-M", "none", "-netdev", "help")
168+
cmd.Stdout = &stdout
169+
cmd.Stderr = &stderr
170+
if err := cmd.Run(); err != nil {
171+
return nil, fmt.Errorf("failed to run %v: stdout=%q, stderr=%q", cmd.Args, stdout.String(), stderr.String())
172+
}
173+
f.NetdevHelp = stdout.Bytes()
174+
return &f, nil
175+
}
176+
139177
func Cmdline(cfg Config) (string, []string, error) {
140178
y := cfg.LimaYAML
141179
exe, args, err := getExe(y.Arch)
142180
if err != nil {
143181
return "", nil, err
144182
}
145183

184+
features, err := inspectFeatures(exe)
185+
if err != nil {
186+
return "", nil, err
187+
}
188+
146189
// Architecture
147190
accel := getAccel(y.Arch)
191+
if !strings.Contains(string(features.AccelHelp), accel) {
192+
errStr := fmt.Sprintf("accelerator %q is not supported by %s", accel, exe)
193+
if accel == "hvf" && y.Arch == limayaml.AARCH64 {
194+
errStr += " ( Hint: as of August 2021, qemu-system-aarch64 on ARM Mac needs to be patched for enabling hvf accelerator,"
195+
errStr += " see https://gist.github.com/nrjdalal/e70249bb5d2e9d844cc203fd11f74c55 )"
196+
}
197+
return "", nil, errors.New(errStr)
198+
}
148199
switch y.Arch {
149200
case limayaml.X8664:
150201
// NOTE: "-cpu host" seems to cause kernel panic
@@ -202,11 +253,29 @@ func Cmdline(cfg Config) (string, []string, error) {
202253
args = append(args, "-netdev", fmt.Sprintf("user,id=net0,net=%s,dhcpstart=%s,hostfwd=tcp:127.0.0.1:%d-:22",
203254
qemuconst.SlirpNetwork, qemuconst.SlirpIPAddress, y.SSH.LocalPort))
204255
args = append(args, "-device", "virtio-net-pci,netdev=net0,mac="+limayaml.MACAddress(cfg.InstanceDir))
256+
if len(y.Network.VDE) > 0 && !strings.Contains(string(features.NetdevHelp), "vde") {
257+
return "", nil, fmt.Errorf("netdev \"vde\" is not supported by %s ( Hint: recompile QEMU with `configure --enable-vde` )", exe)
258+
}
205259
for i, vde := range y.Network.VDE {
206260
// VDE4 accepts VNL like vde:///var/run/vde.ctl as well as file path like /var/run/vde.ctl .
207261
// VDE2 only accepts the latter form.
208262
// VDE2 supports macOS but VDE4 does not yet, so we trim vde:// prefix here for VDE2 compatibility.
209263
vdeSock := strings.TrimPrefix(vde.VNL, "vde://")
264+
if !strings.Contains(vdeSock, "://") {
265+
if _, err := os.Stat(vdeSock); err != nil {
266+
return "", nil, fmt.Errorf("cannot use VNL %q: %w", vde.VNL, err)
267+
}
268+
// vdeSock is a directory, unless vde.SwitchPort == 65535 (PTP)
269+
actualSocket := filepath.Join(vdeSock, "ctl")
270+
if vde.SwitchPort == 65535 { // PTP
271+
actualSocket = vdeSock
272+
}
273+
if st, err := os.Stat(actualSocket); err != nil {
274+
return "", nil, fmt.Errorf("cannot use VNL %q: failed to stat %q: %w", vde.VNL, actualSocket, err)
275+
} else if st.Mode()&fs.ModeSocket == 0 {
276+
return "", nil, fmt.Errorf("cannot use VNL %q: %q is not a socket: %w", vde.VNL, actualSocket, err)
277+
}
278+
}
210279
args = append(args, "-netdev", fmt.Sprintf("vde,id=net%d,sock=%s", i+1, vdeSock))
211280
args = append(args, "-device", fmt.Sprintf("virtio-net-pci,netdev=net%d,mac=%s", i+1, vde.MACAddress))
212281
}

pkg/start/start.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,26 @@ func Start(ctx context.Context, inst *store.Instance) error {
8989
return err
9090
}
9191

92-
return watchHostAgentEvents(ctx, inst.Name, haStdoutPath, haStderrPath, begin)
93-
// leave the hostagent process running
92+
watchErrCh := make(chan error)
93+
go func() {
94+
watchErrCh <- watchHostAgentEvents(ctx, inst.Name, haStdoutPath, haStderrPath, begin)
95+
close(watchErrCh)
96+
}()
97+
waitErrCh := make(chan error)
98+
go func() {
99+
waitErrCh <- haCmd.Wait()
100+
close(waitErrCh)
101+
}()
102+
103+
select {
104+
case watchErr := <-watchErrCh:
105+
// watchErr can be nil
106+
return watchErr
107+
// leave the hostagent process running
108+
case waitErr := <-waitErrCh:
109+
// waitErr should not be nil
110+
return fmt.Errorf("host agent process has exited: %w", waitErr)
111+
}
94112
}
95113

96114
func waitHostAgentStart(ctx context.Context, haPIDPath, haStderrPath string) error {

0 commit comments

Comments
 (0)